wrapc 0.1.1

A zero-fuss parser for rustc arguments, designed for RUSTC_WRAPPER tools
Documentation

wrapc

A zero-fuss parser for rustc arguments, designed for RUSTC_WRAPPER tools. Provides comprehensive and type-safe coverage of rustc's CLI surface area.

Usage Guide: Building Bulletproof RUSTC_WRAPPER Tools

1. The Core Concept

When Cargo invokes a RUSTC_WRAPPER, it doesn't just pass rustc arguments. It uses a specific protocol: <wrapper_binary> - <actual_rustc_path> <rustc_args...>

wrapc automatically detects this protocol, extracts the real rustc path, and parses the remaining arguments into a strongly-typed Info struct.

Experimental: If invoked directly (without Cargo's - separator), it gracefully falls back to standard parsing.

2. Basic Setup & Execution

Add wrapc to your wrapper tool's dependencies:

cargo add wrapc

Here is the canonical skeleton for a RUSTC_WRAPPER executable:

use std::process::Command;
use wrapc::fetch;

fn main() {
    // `fetch()` reads `std::env::args()` and parses them.
    let info = fetch().expect("Failed to parse rustc arguments");
    
    // 1. Handle passthrough commands (help/version)
    if info.help || info.version {
        // Just execute rustc and exit
    }

    // 2. Inspect the compilation context
    println!("Wrapper: Compiling crate {:?}", info.crate_name);
    println!("Wrapper: Target is {:?}", info.target);

    // 3. Determine the real rustc path
    // If invoked via Cargo, `info.rustc` contains the path. 
    // Otherwise, fallback to "rustc" in PATH.
    let rustc_path = info.rustc.unwrap_or_else(|| "rustc".to_string());

    // 4. Reconstruct the arguments perfectly
    let args = info.to_args();

    // 5. Execute the actual compiler
    let status = Command::new(rustc_path)
        .args(&args)
        .status()
        .expect("Failed to spawn rustc");

    std::process::exit(status.code().unwrap_or(1));
}

3. Inspecting Parsed Data

wrapc categorizes rustc flags into logical, type-safe collections.

Configurations and Crate Metadata

// --cfg and --check-cfg
for cfg in &info.configs {
    if cfg == "test" {
        println!("Compiling in test mode!");
    }
}

// --crate-type (e.g., bin, rlib, dylib)
if info.crate_types.contains(&"dylib".to_string()) {
    println!("Building a dynamic library.");
}

Complex Linking (-l and -L)

rustc's linking flags are highly structured. wrapc parses modifiers, kinds, and renames automatically.

use wrapc::{LibrarySearchPathKind, LSPK, LinkLibKind};

// Inspecting -L (Library Search Paths)
for lib_path in &info.libpaths {
    match lib_path.kind {
        LibrarySearchPathKind::Native => println!("Native search path: {:?}", lib_path.path),
        // LSPK is an alias to LibrarySearchPathKind
        LSPK::Framework => println!("macOS Framework path: {:?}", lib_path.path),
        _ => println!("Generic search path: {:?}", lib_path.path),
    }
}

// Inspecting -l (Link Libraries)
// Example parsed: `-lstatic:+bundle,+whole-archive=mylib:renamed_lib`
for link in &info.links {
    println!("Linking: {}", link.name);
    if let Some(kind) = &link.kind {
        println!("  Kind: {:?}", kind); // e.g., LinkLibKind::Static
    }
    if !link.modifiers.is_empty() {
        println!("  Modifiers: {:?}", link.modifiers); // e.g., ["+bundle", "+whole-archive"]
    }
    if let Some(rename) = &link.rename {
        println!("  Renamed to: {}", rename);
    }
}

4. Mutating Arguments (The Wrapper Superpower)

The most common use case for a wrapper is to inject or modify flags before passing them to rustc. Because [Info] is just a standard Rust struct, you can mutate it freely. [Info::to_args()] will flawlessly reconstruct the CLI string.

let mut info = fetch().unwrap();

// Inject a custom linker argument
info.codegen_opts.push("link-arg=-Wl,-rpath,/opt/my-tool/lib".to_string());

// Force a specific optimization level for a specific crate
if info.crate_name.as_deref() == Some("legacy_crate") {
    // Remove existing opt-levels
    info.codegen_opts.retain(|opt| !opt.starts_with("opt-level="));
    // Inject our own
    info.codegen_opts.push("opt-level=1".to_string());
}

// Add a custom configuration flag
info.configs.push("my_wrapper_active".to_string());

// Pass to rustc
let args = info.to_args();

5. Handling Edge Cases & "Unknown" Flags

rustc has hundreds of flags, including unstable nightly flags (-Z) and internal compiler queries. wrapc captures everything it explicitly knows, and safely buckets the rest into info.unknown.

The -- Separator

In rustc, anything after -- is usually ignored or passed to specific internal tools. wrapc stops parsing at -- and dumps the rest into [Info::unknown] to prevent accidental misinterpretation.

// If rustc is called with: `rustc --crate-name foo -- --some-internal-flag`
assert_eq!(info.crate_name, Some("foo".to_string()));
assert_eq!(info.unknown, vec!["--", "--some-internal-flag"]);

Unrecognized / Nightly Flags

If a user passes a brand new nightly flag that wrapc hasn't explicitly modeled yet, it won't crash. It goes to [Info::unknown] and is perfectly preserved by [Info::to_args()].

for unknown_arg in &info.unknown {
    // Safe to pass through to rustc
    println!("Passing through: {}", unknown_arg);
}

The stdin Edge Case (-)

Tools like cargo or IDEs sometimes pass - as the input file to tell rustc to read source code from stdin. Because Cargo's wrapper protocol also uses - as a separator (<wrapper> - <rustc>), naive parsers break here. wrapc handles this contextually:

// Invocation: `my_wrapper - rustc -`
let info = fetch().unwrap();

assert_eq!(info.rustc, Some("rustc".to_string()));
assert_eq!(info.inputs, vec![std::path::PathBuf::from("-")]); // Correctly identified as input!

6. Testing Your Wrapper

To test your wrapper locally without publishing it, use the RUSTC_WRAPPER environment variable:

# Build your wrapper
cargo build --release

# Point Cargo to your wrapper
export RUSTC_WRAPPER="/path/to/your/project/target/release/my_wrapper"

# Run a standard cargo command
cargo build

Your wrapper will intercept every rustc invocation, allowing you to log, mutate, or cache the compilation process using the type-safe data provided by wrapc.