Ctrl K
ComunidadGuías…

scripts/dev-cache.sh

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/bin/sh
# Portable opt-in Cargo cache topology for Codewhale worktrees.
#
# Isolated per-workspace intermediates (Cargo build-dir + {workspace-path-hash})
# plus optional shared sccache. Never hard-codes a machine path. Does not
# change product behavior. This file is safe to source from other /bin/sh
# helpers; the CLI path is the only one that calls `set -eu` and `exit`.
#
# Usage:
#   . scripts/dev-cache.sh && codewhale_dev_cache_apply
#   scripts/dev-cache.sh --status
#   scripts/dev-cache.sh --self-check
#   scripts/dev-cache.sh --print-exports
#   scripts/dev-cache.sh --help
#
# Environment:
#   CODEWHALE_CACHE_ROOT        cache root (default:
#                               ${XDG_CACHE_HOME:-$HOME/.cache}/codewhale)
#   CODEWHALE_DEV_CACHE         auto|1|force|local|0   (default auto)
#                               auto/1/force = isolated build-dir (still
#                                       respects an already-set
#                                       CARGO_TARGET_DIR / CARGO_BUILD_BUILD_DIR)
#                               local = keep compiling into ./target
#                               0     = do nothing
#   CODEWHALE_SCCACHE           auto|1|0         (default auto)
#                               auto  = wrap rustc only when incremental is
#                                       already off and sccache is on PATH
#                               1     = request CARGO_INCREMENTAL=0 (unless
#                                       the caller already set it to on) and
#                                       wrap if sccache exists
#                               0     = never wrap
#   CODEWHALE_DEV_CACHE_QUIET   1 to suppress the one-line status
#   CODEWHALE_DEV_CACHE_REPO_ROOT
#                               workspace root used for ./target detection
#                               (default: $PWD)
#   CODEWHALE_RUSTC_COMMIT      override the rustc commit used to namespace
#                               SCCACHE_DIR (tests / offline hosts)
#
# sccache cannot cache incremental units. This helper never enables the
# wrapper while CARGO_INCREMENTAL is on, and it never turns incremental off
# unless CODEWHALE_SCCACHE=1. Everyday edits stay on cargo's incremental
# default. New-worktree / CI-like rebuilds that already set
# CARGO_INCREMENTAL=0 get the measured shared-compiler-output path.

codewhale_dev_cache_truthy() {
  case ${1:-} in
    1|true|yes|on|force) return 0 ;;
    *) return 1 ;;
  esac
}

codewhale_dev_cache_falsey() {
  case ${1:-} in
    0|false|no|off|never|disabled) return 0 ;;
    *) return 1 ;;
  esac
}

codewhale_dev_cache_root() {
  if [ -n "${CODEWHALE_CACHE_ROOT:-}" ]; then
    printf '%s\n' "$CODEWHALE_CACHE_ROOT"
    return
  fi
  if [ -n "${XDG_CACHE_HOME:-}" ]; then
    printf '%s\n' "$XDG_CACHE_HOME/codewhale"
    return
  fi
  if [ -n "${HOME:-}" ]; then
    printf '%s\n' "$HOME/.cache/codewhale"
    return
  fi
  printf '%s\n' "${TMPDIR:-/tmp}/codewhale-cache"
}

codewhale_dev_cache_repo_root() {
  if [ -n "${CODEWHALE_DEV_CACHE_REPO_ROOT:-}" ]; then
    printf '%s\n' "$CODEWHALE_DEV_CACHE_REPO_ROOT"
    return
  fi
  pwd
}

codewhale_dev_cache_rustc_commit() {
  if [ -n "${CODEWHALE_RUSTC_COMMIT:-}" ]; then
    printf '%s\n' "$CODEWHALE_RUSTC_COMMIT"
    return
  fi
  # rustc -vV is cheap and does not start a compile.
  _cw_out=$(rustc -vV 2>/dev/null || true)
  _cw_commit=$(printf '%s\n' "$_cw_out" | awk '/^commit-hash:/{print $2; exit}')
  if [ -n "$_cw_commit" ]; then
    printf '%s\n' "$_cw_commit"
  else
    printf '%s\n' "unknown"
  fi
}

codewhale_dev_cache_cargo_version_mm() {
  # cargo 1.97.0 -> 1097. Empty if cargo is missing or unparsable.
  cargo --version 2>/dev/null | awk '{
    split($2, a, ".")
    if (a[1] ~ /^[0-9]+$/ && a[2] ~ /^[0-9]+$/)
      printf "%d\n", a[1]*1000+a[2]
  }'
}

# Stable 16-hex-char fingerprint of a path. Used only for the pre-1.91
# CARGO_TARGET_DIR fallback; Cargo 1.91+ expands {workspace-path-hash}.
codewhale_dev_cache_path_fingerprint() {
  if command -v sha256sum >/dev/null 2>&1; then
    printf '%s' "$1" | sha256sum | awk '{print substr($1,1,16)}'
  elif command -v shasum >/dev/null 2>&1; then
    printf '%s' "$1" | shasum -a 256 | awk '{print substr($1,1,16)}'
  elif command -v openssl >/dev/null 2>&1; then
    printf '%s' "$1" | openssl dgst -sha256 | awk '{print substr($NF,1,16)}'
  else
    printf '%s' "$1" | cksum | awk '{printf "%s%08s\n", $1, $2}' | tr ' ' '0'
  fi
}

codewhale_dev_cache_incremental_off() {
  case ${CARGO_INCREMENTAL:-} in
    0|false|no|off) return 0 ;;
    *) return 1 ;;
  esac
}

codewhale_dev_cache_incremental_on() {
  case ${CARGO_INCREMENTAL:-} in
    1|true|yes|on) return 0 ;;
    *) return 1 ;;
  esac
}

codewhale_dev_cache_sccache_bin() {
  command -v sccache 2>/dev/null || true
}

codewhale_dev_cache_log() {
  if [ "${CODEWHALE_DEV_CACHE_QUIET:-}" = 1 ]; then
    return 0
  fi
  printf 'dev-cache: %s\n' "$*" >&2
}

# Free space (in GiB, integer, truncated) on the filesystem holding $1, or
# empty when it cannot be determined. `df -Pk` is the POSIX-portable form and
# behaves the same on macOS and Linux.
codewhale_dev_cache_free_gib() {
  _cw_probe=$1
  # Walk up to the nearest existing ancestor: the cache root usually does not
  # exist yet on a first run, and df needs a real path.
  while [ -n "$_cw_probe" ] && [ ! -d "$_cw_probe" ]; do
    _cw_parent=$(dirname -- "$_cw_probe")
    [ "$_cw_parent" = "$_cw_probe" ] && break
    _cw_probe=$_cw_parent
  done
  [ -d "$_cw_probe" ] || return 0
  df -Pk -- "$_cw_probe" 2>/dev/null | awk 'NR==2 { print int($4 / 1048576) }'
}

# A cold workspace build-dir is ~6 GB, and cargo writes it incrementally, so a
# volume that runs out mid-build leaves a half-written cache and a failed run.
# Worse, on the host where this was added the low volume was also $TMPDIR's:
# the shell tool's spill `tempfile` fails on a full disk, which is the exact
# wedge #5465 was filed for. Warn before the build starts rather than after it
# dies, and name the override that fixes it.
CODEWHALE_DEV_CACHE_MIN_FREE_GIB=${CODEWHALE_DEV_CACHE_MIN_FREE_GIB:-15}

codewhale_dev_cache_warn_low_space() {
  _cw_root=$1
  _cw_free=$(codewhale_dev_cache_free_gib "$_cw_root")
  [ -n "$_cw_free" ] || return 0
  [ "$_cw_free" -lt "$CODEWHALE_DEV_CACHE_MIN_FREE_GIB" ] || return 0
  codewhale_dev_cache_log \
    "WARNING: only ${_cw_free}GiB free on the volume holding ${_cw_root}; a cold build-dir is ~6GB."
  codewhale_dev_cache_log \
    "         Set CODEWHALE_CACHE_ROOT=<dir on a roomier volume> (threshold: CODEWHALE_DEV_CACHE_MIN_FREE_GIB)."
}

# Apply the topology to the current shell. Idempotent. Never overrides an
# already-set CARGO_TARGET_DIR, CARGO_BUILD_BUILD_DIR, RUSTC_WRAPPER, or
# SCCACHE_DIR.
codewhale_dev_cache_apply() {
  CODEWHALE_DEV_CACHE_MODE=${CODEWHALE_DEV_CACHE_MODE:-}
  # Re-entry after a previous apply in this shell: keep the first decision
  # unless the caller unset the mode (tests do that between cases).
  if [ -n "${CODEWHALE_DEV_CACHE_APPLIED:-}" ] && [ -n "${CODEWHALE_DEV_CACHE_MODE:-}" ]; then
    return 0
  fi

  _cw_want=${CODEWHALE_DEV_CACHE:-auto}
  _cw_root=$(codewhale_dev_cache_root)
  _cw_sccache_want=${CODEWHALE_SCCACHE:-auto}
  _cw_commit=$(codewhale_dev_cache_rustc_commit)
  CODEWHALE_DEV_CACHE_SCCACHE_DIR=${SCCACHE_DIR:-${_cw_root}/sccache/${_cw_commit}}
  CODEWHALE_DEV_CACHE_BUILD_DIR=${_cw_root}/build/'{workspace-path-hash}'
  CODEWHALE_DEV_CACHE_LEGACY_TARGET_DIR=${_cw_root}/target/$(codewhale_dev_cache_path_fingerprint "$(codewhale_dev_cache_repo_root)")

  if codewhale_dev_cache_falsey "$_cw_want"; then
    CODEWHALE_DEV_CACHE_MODE=disabled
    CODEWHALE_DEV_CACHE_SCCACHE=disabled
    CODEWHALE_DEV_CACHE_APPLIED=1
    export CODEWHALE_DEV_CACHE_MODE CODEWHALE_DEV_CACHE_SCCACHE
    codewhale_dev_cache_log "disabled (CODEWHALE_DEV_CACHE=${_cw_want})"
    return 0
  fi

  if [ -n "${CARGO_TARGET_DIR:-}" ]; then
    CODEWHALE_DEV_CACHE_MODE=inherited-target-dir
  elif [ -n "${CARGO_BUILD_BUILD_DIR:-}" ]; then
    CODEWHALE_DEV_CACHE_MODE=inherited-build-dir
  elif [ "$_cw_want" = local ]; then
    # Explicit stay-in-./target. A stub target/ created by a previous
    # isolated build-dir run is not a reason to abandon isolation: Cargo
    # still writes CACHEDIR.TAG (and sometimes finals) under ./target
    # when build-dir is split, and treating that as "warm" made the
    # second command recompile everything into a second tree.
    CODEWHALE_DEV_CACHE_MODE=existing-target
  else
    CODEWHALE_DEV_CACHE_MODE=isolated-build-dir
  fi

  case $CODEWHALE_DEV_CACHE_MODE in
    force-isolated|isolated-build-dir)
      _cw_mm=$(codewhale_dev_cache_cargo_version_mm)
      if [ -n "$_cw_mm" ] && [ "$_cw_mm" -lt 1091 ]; then
        # Cargo 1.91 introduced build.build-dir. Older cargo only isolates
        # via CARGO_TARGET_DIR; keep that fallback per-workspace.
        if [ -z "${CARGO_TARGET_DIR:-}" ]; then
          CARGO_TARGET_DIR=$CODEWHALE_DEV_CACHE_LEGACY_TARGET_DIR
          export CARGO_TARGET_DIR
        fi
        CODEWHALE_DEV_CACHE_MODE=${CODEWHALE_DEV_CACHE_MODE}-legacy-target-dir
      else
        CARGO_BUILD_BUILD_DIR=$CODEWHALE_DEV_CACHE_BUILD_DIR
        export CARGO_BUILD_BUILD_DIR
        mkdir -p "$_cw_root/build" 2>/dev/null || true
      fi
      ;;
  esac

  # sccache: never wrap incremental rustc. Missing binary is a fallback,
  # not an error.
  if codewhale_dev_cache_falsey "$_cw_sccache_want"; then
    CODEWHALE_DEV_CACHE_SCCACHE=disabled
  elif [ -n "${RUSTC_WRAPPER:-}" ]; then
    case $RUSTC_WRAPPER in
      *sccache*) CODEWHALE_DEV_CACHE_SCCACHE=external-sccache ;;
      *) CODEWHALE_DEV_CACHE_SCCACHE=external-wrapper ;;
    esac
  else
    if codewhale_dev_cache_truthy "$_cw_sccache_want" && ! codewhale_dev_cache_incremental_on; then
      # Request the measured new-worktree / CI-like topology.
      if [ -z "${CARGO_INCREMENTAL:-}" ]; then
        CARGO_INCREMENTAL=0
        export CARGO_INCREMENTAL
      fi
    fi
    if ! codewhale_dev_cache_incremental_off; then
      CODEWHALE_DEV_CACHE_SCCACHE=skipped-incremental
    else
      _cw_bin=$(codewhale_dev_cache_sccache_bin)
      if [ -z "$_cw_bin" ]; then
        CODEWHALE_DEV_CACHE_SCCACHE=not-found
      else
        RUSTC_WRAPPER=$_cw_bin
        export RUSTC_WRAPPER
        if [ -z "${SCCACHE_DIR:-}" ]; then
          SCCACHE_DIR=$CODEWHALE_DEV_CACHE_SCCACHE_DIR
          export SCCACHE_DIR
        fi
        mkdir -p "$SCCACHE_DIR" 2>/dev/null || true
        CODEWHALE_DEV_CACHE_SCCACHE=enabled
      fi
    fi
  fi

  CODEWHALE_DEV_CACHE_APPLIED=1
  export CODEWHALE_DEV_CACHE_MODE CODEWHALE_DEV_CACHE_SCCACHE
  export CODEWHALE_DEV_CACHE_SCCACHE_DIR

  _cw_line="mode=${CODEWHALE_DEV_CACHE_MODE} sccache=${CODEWHALE_DEV_CACHE_SCCACHE}"
  case $CODEWHALE_DEV_CACHE_MODE in
    isolated-build-dir*|force-isolated*)
      _cw_line="${_cw_line} build-dir=${CARGO_BUILD_BUILD_DIR:-${CARGO_TARGET_DIR:-unset}}"
      ;;
    inherited-target-dir)
      _cw_line="${_cw_line} CARGO_TARGET_DIR=${CARGO_TARGET_DIR}"
      ;;
    inherited-build-dir)
      _cw_line="${_cw_line} CARGO_BUILD_BUILD_DIR=${CARGO_BUILD_BUILD_DIR}"
      ;;
    existing-target)
      _cw_line="${_cw_line} using ./target (CODEWHALE_DEV_CACHE=local)"
      ;;
  esac
  if [ "${CODEWHALE_DEV_CACHE_SCCACHE}" = enabled ]; then
    _cw_line="${_cw_line} SCCACHE_DIR=${SCCACHE_DIR} rustc=${_cw_commit}"
  fi
  codewhale_dev_cache_log "$_cw_line"
  case $CODEWHALE_DEV_CACHE_MODE in
    disabled|existing-target) ;;
    *) codewhale_dev_cache_warn_low_space "$(codewhale_dev_cache_root)" ;;
  esac
}

codewhale_dev_cache_status() {
  _cw_tmpl=${CODEWHALE_DEV_CACHE_BUILD_DIR:-}
  if [ -z "$_cw_tmpl" ]; then
    _cw_tmpl=$(codewhale_dev_cache_root)/build/'{workspace-path-hash}'
  fi
  printf '%s\n' \
    "cache_root=$(codewhale_dev_cache_root)" \
    "repo_root=$(codewhale_dev_cache_repo_root)" \
    "mode=${CODEWHALE_DEV_CACHE_MODE:-unset}" \
    "sccache=${CODEWHALE_DEV_CACHE_SCCACHE:-unset}" \
    "build_dir_template=${_cw_tmpl}" \
    "CARGO_BUILD_BUILD_DIR=${CARGO_BUILD_BUILD_DIR:-<unset>}" \
    "CARGO_TARGET_DIR=${CARGO_TARGET_DIR:-<unset>}" \
    "CARGO_INCREMENTAL=${CARGO_INCREMENTAL:-<unset>}" \
    "RUSTC_WRAPPER=${RUSTC_WRAPPER:-<unset>}" \
    "SCCACHE_DIR=${SCCACHE_DIR:-<unset>}" \
    "rustc_commit=$(codewhale_dev_cache_rustc_commit)" \
    "sccache_bin=$(codewhale_dev_cache_sccache_bin)"
}

# Extra cargo CLI flags that force template expansion of build-dir.
# CARGO_BUILD_BUILD_DIR is still exported for nested tools; --config is
# what Cargo documents as supporting {workspace-path-hash}.
codewhale_dev_cache_exec_cargo() {
  case ${CODEWHALE_DEV_CACHE_MODE:-} in
    isolated-build-dir|force-isolated)
      if [ -n "${CARGO_BUILD_BUILD_DIR:-}" ]; then
        set -- --config "build.build-dir = \"${CARGO_BUILD_BUILD_DIR}\"" "$@"
      fi
      ;;
  esac
  # One Cargo build per machine: separate target dirs do not make concurrent
  # builds safe on a memory-constrained host (scripts/build-lock.py).
  _cw_lock_script=${repo_root:-.}/scripts/build-lock.py
  if ! codewhale_dev_cache_falsey "${CODEWHALE_BUILD_LOCK:-1}" \
    && [ -z "${CODEWHALE_BUILD_LOCK_HELD:-}" ] \
    && [ -f "$_cw_lock_script" ] \
    && command -v python3 >/dev/null 2>&1; then
    CARGOWHALE=off exec python3 "$_cw_lock_script" "${CODEWHALE_BUILD_LOCK_FILE:-$(codewhale_dev_cache_root)/build.lock}" -- cargo "$@"
  fi
  exec cargo "$@"
}

codewhale_dev_cache_print_exports() {
  [ -n "${CARGO_BUILD_BUILD_DIR:-}" ] && printf 'export CARGO_BUILD_BUILD_DIR=%s\n' "$CARGO_BUILD_BUILD_DIR"
  [ -n "${CARGO_TARGET_DIR:-}" ] && printf 'export CARGO_TARGET_DIR=%s\n' "$CARGO_TARGET_DIR"
  [ -n "${CARGO_INCREMENTAL:-}" ] && printf 'export CARGO_INCREMENTAL=%s\n' "$CARGO_INCREMENTAL"
  [ -n "${RUSTC_WRAPPER:-}" ] && printf 'export RUSTC_WRAPPER=%s\n' "$RUSTC_WRAPPER"
  [ -n "${SCCACHE_DIR:-}" ] && printf 'export SCCACHE_DIR=%s\n' "$SCCACHE_DIR"
  [ -n "${CODEWHALE_DEV_CACHE_MODE:-}" ] && printf 'export CODEWHALE_DEV_CACHE_MODE=%s\n' "$CODEWHALE_DEV_CACHE_MODE"
  [ -n "${CODEWHALE_DEV_CACHE_SCCACHE:-}" ] && printf 'export CODEWHALE_DEV_CACHE_SCCACHE=%s\n' "$CODEWHALE_DEV_CACHE_SCCACHE"
}

codewhale_dev_cache_self_check() {
  _cw_fail=0
  _cw_root=$(codewhale_dev_cache_root)
  _cw_home=${HOME:-}
  _cw_xdg=${XDG_CACHE_HOME:-}

  if [ -z "${CODEWHALE_CACHE_ROOT:-}" ]; then
    if [ -n "$_cw_xdg" ]; then
      case $_cw_root in
        "$_cw_xdg"/codewhale) ;;
        *)
          printf 'self-check: expected XDG default %s/codewhale, got %s\n' "$_cw_xdg" "$_cw_root" >&2
          _cw_fail=1
          ;;
      esac
    elif [ -n "$_cw_home" ]; then
      case $_cw_root in
        "$_cw_home"/.cache/codewhale) ;;
        *)
          printf 'self-check: expected HOME default %s/.cache/codewhale, got %s\n' "$_cw_home" "$_cw_root" >&2
          _cw_fail=1
          ;;
      esac
    fi
  fi

  case ${CARGO_BUILD_BUILD_DIR:-} in
    *'{workspace-path-hash}'*)
      ;;
    '')
      case ${CODEWHALE_DEV_CACHE_MODE:-} in
        isolated-build-dir|force-isolated)
          printf 'self-check: isolated mode did not set CARGO_BUILD_BUILD_DIR\n' >&2
          _cw_fail=1
          ;;
      esac
      ;;
    *)
      case ${CODEWHALE_DEV_CACHE_MODE:-} in
        isolated-build-dir|force-isolated)
          printf 'self-check: isolated build-dir lacks {workspace-path-hash}: %s\n' "$CARGO_BUILD_BUILD_DIR" >&2
          _cw_fail=1
          ;;
      esac
      ;;
  esac

  if [ "${CODEWHALE_DEV_CACHE_SCCACHE:-}" = enabled ]; then
    if ! codewhale_dev_cache_incremental_off; then
      printf 'self-check: sccache enabled while incremental is not off\n' >&2
      _cw_fail=1
    fi
    case ${RUSTC_WRAPPER:-} in
      *sccache*) ;;
      *)
        printf 'self-check: sccache enabled but RUSTC_WRAPPER=%s\n' "${RUSTC_WRAPPER:-<unset>}" >&2
        _cw_fail=1
        ;;
    esac
  fi

  if [ "${CODEWHALE_DEV_CACHE_SCCACHE:-}" = not-found ]; then
    if [ -n "${RUSTC_WRAPPER:-}" ]; then
      printf 'self-check: missing sccache must not set RUSTC_WRAPPER (%s)\n' "$RUSTC_WRAPPER" >&2
      _cw_fail=1
    fi
  fi

  if [ "$_cw_fail" -eq 0 ]; then
    printf 'dev-cache: self-check ok\n'
    return 0
  fi
  printf 'dev-cache: self-check FAILED\n' >&2
  return 1
}

codewhale_dev_cache_usage() {
  cat <<'EOF'
usage: scripts/dev-cache.sh --status|--self-check|--print-exports|--help

Portable opt-in Cargo cache topology. Source this file and call
codewhale_dev_cache_apply from scripts/dev-test.sh / scripts/dev-cargo.sh.

  --status          apply (if needed) and print key=value status
  --self-check      apply and check portable defaults / sccache fallback
  --print-exports   apply and print export statements for eval
  --help            this message

Environment overrides are documented at the top of this file. Defaults
never contain a machine-specific absolute path.
EOF
}

# CLI only when this file is the executed program, not when sourced.
case ${0##*/} in
  dev-cache.sh)
    set -eu
    _cw_cmd=${1:---status}
    case $_cw_cmd in
      --status|-s)
        codewhale_dev_cache_apply
        codewhale_dev_cache_status
        ;;
      --self-check)
        codewhale_dev_cache_apply
        codewhale_dev_cache_status
        codewhale_dev_cache_self_check
        ;;
      --print-exports)
        codewhale_dev_cache_apply
        codewhale_dev_cache_print_exports
        ;;
      --help|-h)
        codewhale_dev_cache_usage
        ;;
      *)
        codewhale_dev_cache_usage >&2
        exit 2
        ;;
    esac
    ;;
esac