cargo-fixup 0.1.1

A Cargo helper that installs a rustc-wrapper to dynamically patch crate source code at build time — no need to modify Cargo.toml or maintain forks.
use std::io::Write;
use std::os::unix::fs::PermissionsExt;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mut args = std::env::args().peekable();
    let _command = args.next();
    args.next_if(|x| x.as_str() == "fixup");

    let log_path = args.next().unwrap_or_else(|| String::from("/dev/null"));

    let metadata = cargo_metadata::MetadataCommand::new().no_deps().exec()?;

    let workspace_root = metadata.workspace_root;

    let cargo_dir = workspace_root.join(".cargo");
    std::fs::create_dir_all(&cargo_dir)?;

    // Write config.toml
    let config_path = cargo_dir.join("config.toml");
    let config_contents = r#"[build]
rustc-wrapper = "./.cargo/rustc-wrapper.sh"
"#;
    std::fs::write(&config_path, config_contents)?;

    // Write rustc-wrapper.sh
    let wrapper_path = cargo_dir.join("rustc-wrapper.sh");
    let script = format!(
        r#"#!/bin/sh

# Redirect all output to log file except for the final exec'd command
LOG_FILE="{log_path}"
exec 3>&1 4>&2
exec >>"$LOG_FILE" 2>&1

set -eu

# --- Constants and Paths ---
SCRIPT_DIR=$(CDPATH= cd -- "$(dirname -- "$0")/../" && pwd)
PATCHES_DIR="$SCRIPT_DIR/patches"
TARGET_PATCHED_DIR="$SCRIPT_DIR/target/patched-crates"

# --- No patching needed - run original args ---
if [ -z "${{CARGO_PKG_NAME:-}}" ] || [ -z "${{CARGO_MANIFEST_DIR:-}}" ]; then
  exec 1>&3 2>&4
  exec "$@"
fi

ORIGINAL_DIR_NAME=$(basename "$CARGO_MANIFEST_DIR")
PATCH_DIR="$PATCHES_DIR/$ORIGINAL_DIR_NAME"

# --- Check for matching patch directory ---
if [ -d "$PATCH_DIR" ]; then
  PATCHED_SRC="$TARGET_PATCHED_DIR/$ORIGINAL_DIR_NAME"

  echo "Applying patches to $CARGO_PKG_NAME..."

  mkdir -p "$TARGET_PATCHED_DIR"
  rm -rf -- "$PATCHED_SRC"
  cp -a -- "$CARGO_MANIFEST_DIR" "$PATCHED_SRC"

  for PATCH_FILE in "$PATCH_DIR"/*; do
    [ -f "$PATCH_FILE" ] || continue
    echo "Applying patch: $PATCH_FILE"
    patch -s -p1 -d "$PATCHED_SRC" < "$PATCH_FILE"
  done

  new_args=()
  for arg in "$@"; do
    new_args+=("${{arg//$CARGO_MANIFEST_DIR/$PATCHED_SRC}}")
  done

  exec 1>&3 2>&4
  exec "${{new_args[@]}}"
else
  exec 1>&3 2>&4
  exec "$@"
fi
"#,
    );

    let mut file = std::fs::File::create(&wrapper_path)?;
    file.write_all(script.as_bytes())?;
    let mut perms = file.metadata()?.permissions();
    perms.set_mode(0o755);
    std::fs::set_permissions(&wrapper_path, perms)?;

    println!("Patch wrapper configured in: {}", cargo_dir);
    Ok(())
}