wrapc 0.1.2

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

wrapc

Crates.io Documentation License: MIT

A zero-fuss, type-safe parser for rustc arguments, designed specifically for RUSTC_WRAPPER tools.


Overview

Building tools that intercept Rust compilation (such as compilation caches, custom profilers, static analyzers, or linker-flag injectors) requires parsing rustc's command-line arguments.

However, rustc's CLI is notoriously complex. It mixes = and space-separated values, features intricate sub-syntaxes for linking (-l kind:+modifiers=name:rename), and constantly evolves with new nightly flags. Standard CLI parsers like clap are too rigid and bloated for this specific use case.

wrapc solves this by providing a comprehensive, strongly-typed, and easily extensible parser that perfectly understands the RUSTC_WRAPPER protocol, guaranteeing flawless round-trip reconstruction of arguments.

Features

  • Wrapper Protocol: handles Cargo's <wrapper> - <rustc> <args...> invocation format.
  • Type-Safe & Comprehensive: Parses complex flags like --emit, --extern, -L, and -l into structured Rust types instead of raw strings.
  • Flawless Round-Tripping: The Info::to_args() method perfectly reconstructs the parsed arguments, ensuring zero data loss when passing them down to the actual rustc.
  • Lightweight: No heavy macro dependencies or clap. Just fast, predictable, and extensible string parsing.
  • Panic-Free: Gracefully handles missing values, unrecognized/nightly flags, and the -- separator without crashing your build pipeline.

Quick Start

Add wrapc to your wrapper tool's dependencies:

cargo add wrapc

The Canonical Wrapper Skeleton

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

fn main() {
    // `fetch()` reads `std::env::args()` and parses them.
    let mut 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 or mutate the compilation context
    if info.crate_name.as_deref() == Some("legacy_crate") {
        info.codegen_opts.push("opt-level=1".to_string());
    }

    // 3. Determine the real rustc 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));
}

Deep Dive: Complex Flags

rustc's linking flags are highly structured. wrapc parses modifiers, kinds, and renames automatically so you don't have to write regexes.

use wrapc::{LibrarySearchPathKind, LinkLibKind};

// Example invocation: 
// rustc -L native=/opt/lib -l static:+bundle,+whole-archive=mylib:renamed

for lib_path in &info.libpaths {
    if lib_path.kind == LibrarySearchPathKind::Native {
        println!("Native search path: {:?}", lib_path.path);
    }
}

for link in &info.links {
    println!("Linking: {}", link.name);
    if let Some(LinkLibKind::Static) = link.kind {
        println!("  Modifiers: {:?}", link.modifiers); // ["+bundle", "+whole-archive"]
        println!("  Renamed to: {:?}", link.rename);   // Some("renamed")
    }
}

The RUSTC_WRAPPER Protocol & Edge Cases

When Cargo invokes a wrapper, it uses a specific protocol: <wrapper_binary> - <actual_rustc_path> <rustc_args...>

wrapc automatically strips the first three arguments and stores the real compiler path in info.rustc.

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, naive parsers break here. wrapc handles this contextually:

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

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

Unrecognized / Nightly Flags

If a user passes a brand-new nightly flag that wrapc hasn't explicitly modeled yet, it won't crash. It safely buckets it into info.unknown and perfectly preserves it during to_args() reconstruction.

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="$(pwd)/target/release/my_wrapper"

# Run a standard cargo command
cargo build

Contributing

Contributions, issues, and feature requests are welcome!
If you notice a missing rustc flag that should be explicitly modeled rather than falling back to unknown, please open an Issue or Merge Request on GitLab.

License

This project is licensed under the MIT License.


Made with ❤️ for Rust community.