File PKGBUILD of Package tools-fish_pgo

# Maintainer: neycrol <330578697@qq.com>
pkgname=fish-git-pgo-bolt
pkgver=4.2.1.r0.g53e6758cc3
pkgrel=17
pkgdesc="Fish Shell (Rust) - PGO + Fat LTO + Codegen=1 + BOLT Optimized"
arch=(x86_64)
url="https://fishshell.com"
license=(GPL2)
depends=(glibc gcc-libs ncurses pcre2)
makedepends=(git cmake ninja llvm-god-mlgo rust gcc-git-god)
provides=(fish)
conflicts=(fish fish-git)
source=(
  "fish-shell.tar.gz"
  "vendor.tar.gz"
)
sha256sums=('SKIP' 'SKIP')

_write_cargo_vendor_config() {
  local _root="$1"
  mkdir -p "$_root/.cargo"
  cat > "$_root/.cargo/config.toml" <<'EOF'
[source.crates-io]
replace-with = "vendored-sources"

[source."git+https://github.com/fish-shell/rust-pcre2?tag=0.2.9-utf32"]
git = "https://github.com/fish-shell/rust-pcre2"
tag = "0.2.9-utf32"
replace-with = "vendored-sources"

[source.vendored-sources]
directory = "vendor"
EOF
}

_sync_cargo_with_vendor() {
  local _root="$srcdir/fish-shell"
  [[ -d "$_root" ]] || return 0

  _write_cargo_vendor_config "$_root"

  (
    cd "$_root"

    local _vendor_root="$PWD/vendor"
    if [[ -d "$_vendor_root/vendor" ]]; then
      _vendor_root="$_vendor_root/vendor"
    fi
    [[ -d "$_vendor_root" ]] || exit 0

    # Some upstream dependency constraints may drift ahead of vendored crates.
    # Pre-sync known hotspot: nix.
    local _vendored_nix_ver=""
    local _vendored_nix_sum=""
    if [[ -f "$_vendor_root/nix/Cargo.toml" ]]; then
      _vendored_nix_ver=$(sed -n 's/^version = \"\\(.*\\)\"/\\1/p' "$_vendor_root/nix/Cargo.toml" | head -n1)
      if [[ -f "$_vendor_root/nix/.cargo-checksum.json" ]]; then
        _vendored_nix_sum=$(sed -n 's/.*\"package\":\"\\([0-9a-f]*\\)\".*/\\1/p' "$_vendor_root/nix/.cargo-checksum.json" | head -n1)
      fi
    fi
    if [[ -n "$_vendored_nix_ver" ]]; then
      local _nix_toml
      for _nix_toml in "$PWD/Cargo.toml" "$PWD/crates/common/Cargo.toml"; do
        [[ -f "$_nix_toml" ]] || continue
        sed -E -i \
          -e "s/^([[:space:]]*nix[[:space:]]*=[[:space:]]*)\"[^\"]+\"/\\1\"^${_vendored_nix_ver}\"/" \
          -e "s/(nix[[:space:]]*=[[:space:]]*\\{[^}]*version[[:space:]]*=[[:space:]]*)\"[^\"]+\"/\\1\"^${_vendored_nix_ver}\"/" \
          "$_nix_toml"
      done
      if [[ -f Cargo.lock ]]; then
        awk -v c="nix" -v v="$_vendored_nix_ver" -v s="$_vendored_nix_sum" '
          BEGIN { in_pkg=0; is_target=0 }
          /^\[\[package\]\]/ { in_pkg=1; is_target=0 }
          in_pkg && $0 == "name = \"" c "\"" { is_target=1 }
          in_pkg && is_target && /^version = / { $0 = "version = \"" v "\"" }
          in_pkg && is_target && s != "" && /^checksum = / { $0 = "checksum = \"" s "\"" }
          { print }
        ' Cargo.lock > Cargo.lock.new && mv Cargo.lock.new Cargo.lock
      fi
    fi

    # Keep Cargo.lock aligned with vendored crates for offline builds.
    # For git-replaced crates, Cargo requires an existing lockfile, so we heal
    # mismatched entries in-place using cargo metadata diagnostics.
    [[ -f Cargo.lock ]] || exit 0
    local _i _meta _crate _cand _sum
    for _i in {1..30}; do
      _meta=$(CARGO_NET_OFFLINE=true cargo metadata --format-version 1 --locked --offline 2>&1 >/dev/null) && break

      _crate=$(printf '%s\n' "$_meta" | sed -n 's/.*requirement .\([^ =]*\) = .*/\1/p' | head -n1)
      _cand=$(printf '%s\n' "$_meta" | sed -n "s/.*candidate versions found which didn't match: \\([^ ,)]*\\).*/\\1/p" | head -n1)
      if [[ -z "$_crate" || -z "$_cand" ]]; then
        break
      fi

      _sum=""
      if [[ -f "$_vendor_root/$_crate/.cargo-checksum.json" ]]; then
        _sum=$(sed -n 's/.*\"package\":\"\\([0-9a-f]*\\)\".*/\\1/p' "$_vendor_root/$_crate/.cargo-checksum.json" | head -n1)
      fi

      awk -v c="$_crate" -v v="$_cand" -v s="$_sum" '
        BEGIN { in_pkg=0; is_target=0 }
        /^\[\[package\]\]/ { in_pkg=1; is_target=0 }
        in_pkg && $0 == "name = \"" c "\"" { is_target=1 }
        in_pkg && is_target && /^version = / { $0 = "version = \"" v "\"" }
        in_pkg && is_target && s != "" && /^checksum = / { $0 = "checksum = \"" s "\"" }
        { print }
      ' Cargo.lock > Cargo.lock.new && mv Cargo.lock.new Cargo.lock
    done
  ) || true
}



pkgver() {
  local _ver _ts
  if [[ -f "${srcdir}/fish-shell.obsinfo" ]]; then
    _ver=$(sed -n 's/^version: //p' "${srcdir}/fish-shell.obsinfo" | head -n1)
  fi
  if [[ -z "$_ver" && -f "${srcdir}/._servicedata" ]]; then
    _ver=$(sed -n 's/.*<param name="version">\(.*\)<\/param>.*/\1/p' "${srcdir}/._servicedata" | head -n1)
  fi
  if [[ -z "$_ver" && -d "${srcdir}/fish-shell" ]]; then
    _ts=$(find "${srcdir}/fish-shell" -type f -printf '%T@\n' 2>/dev/null | sort -n | tail -n1 | cut -d. -f1)
    if [[ -n "$_ts" ]]; then
      _ver=$(date -u -d "@${_ts}" +%Y%m%d.%H%M%S 2>/dev/null)
    fi
  fi
  if [[ -z "$_ver" ]]; then
    _ver="${pkgver}"
  fi
  echo "${_ver//-/_}"
}


prepare() {
  mkdir -p "$srcdir/bolt_data"

  if [ ! -d "$srcdir/fish-shell" ]; then
    _src_dir=$(find "$srcdir" -maxdepth 1 -type d -name "fish-shell-*" | head -n 1)
    if [ -z "$_src_dir" ]; then
      echo "Missing source dir for fish-shell"
      return 1
    fi
    mv "$_src_dir" "$srcdir/fish-shell"
  fi

  if [ -d "$srcdir/vendor" ]; then
    rm -rf "$srcdir/fish-shell/vendor"
    mv "$srcdir/vendor" "$srcdir/fish-shell/vendor"
  fi

  _sync_cargo_with_vendor
}

build() {
  # Prefer llvm-god-mlgo tools when available.
  for d in /opt/llvm-god/bin /usr/lib/llvm*/bin /usr/lib/llvm*; do
    if [[ -x "$d/llvm-bolt" && -x "$d/merge-fdata" && -x "$d/llvm-profdata" ]]; then
      export PATH="$d:$PATH"
      break
    fi
  done

  # OBS often reuses srcdir and may skip prepare(); enforce vendored Rust sync here.
  _sync_cargo_with_vendor

  # fish's test helper target is not needed for runtime packaging and fails to
  # link under our GCC/LTO toolchain in OBS. Drop tests from the CMake graph.
  if [[ -f "$srcdir/fish-shell/CMakeLists.txt" ]]; then
    sed -i '/^include(cmake\/Tests\.cmake)$/d' "$srcdir/fish-shell/CMakeLists.txt"
  fi

  # Keep workspace nix requirement aligned with the currently vendored crate.
  # This runs every build because OBS may reuse srcdir and skip prepare().
  local _nix_ver=""
  local _nix_sum=""
  local _nix_dir=""
  if [[ -f "$srcdir/fish-shell/vendor/nix/Cargo.toml" ]]; then
    _nix_dir="$srcdir/fish-shell/vendor/nix"
  elif [[ -f "$srcdir/fish-shell/vendor/vendor/nix/Cargo.toml" ]]; then
    _nix_dir="$srcdir/fish-shell/vendor/vendor/nix"
  fi
  if [[ -n "$_nix_dir" ]]; then
    _nix_ver=$(sed -n 's/^version = \"\\(.*\\)\"/\\1/p' "$_nix_dir/Cargo.toml" | head -n1)
    if [[ -f "$_nix_dir/.cargo-checksum.json" ]]; then
      _nix_sum=$(sed -n 's/.*\"package\":\"\\([0-9a-f]*\\)\".*/\\1/p' "$_nix_dir/.cargo-checksum.json" | head -n1)
    fi
  fi
  if [[ -n "$_nix_ver" && -f "$srcdir/fish-shell/Cargo.toml" ]]; then
    sed -E -i \
      -e "s/^([[:space:]]*nix[[:space:]]*=[[:space:]]*)\"[^\"]+\"/\\1\"^${_nix_ver}\"/" \
      -e "s/^([[:space:]]*nix[[:space:]]*=[[:space:]]*\\{[^}]*version[[:space:]]*=[[:space:]]*)\"[^\"]+\"/\\1\"^${_nix_ver}\"/" \
      "$srcdir/fish-shell/Cargo.toml"
    msg2 "Forced nix workspace dependency to ^${_nix_ver}"
  fi
  if [[ -n "$_nix_ver" && -f "$srcdir/fish-shell/Cargo.lock" ]]; then
    awk -v c="nix" -v v="$_nix_ver" -v s="$_nix_sum" '
      BEGIN { in_pkg=0; is_target=0 }
      /^\[\[package\]\]/ { in_pkg=1; is_target=0 }
      in_pkg && $0 == "name = \"" c "\"" { is_target=1 }
      in_pkg && is_target && /^version = / { $0 = "version = \"" v "\"" }
      in_pkg && is_target && s != "" && /^checksum = / { $0 = "checksum = \"" s "\"" }
      { print }
    ' "$srcdir/fish-shell/Cargo.lock" > "$srcdir/fish-shell/Cargo.lock.new" && mv "$srcdir/fish-shell/Cargo.lock.new" "$srcdir/fish-shell/Cargo.lock"
  fi

  # Heal remaining lockfile drifts against vendored crates until metadata resolves.
  (
    cd "$srcdir/fish-shell"
    local _vendor_lock_root="$PWD/vendor"
    if [[ -d "$_vendor_lock_root/vendor" ]]; then
      _vendor_lock_root="$_vendor_lock_root/vendor"
    fi
    local _i _meta _crate _cand _sum
    for _i in {1..80}; do
      _meta=$(CARGO_NET_OFFLINE=true cargo metadata --format-version 1 --locked --offline 2>&1 >/dev/null) && break

      _crate=$(printf '%s\n' "$_meta" | sed -n 's/.*requirement .\([^ =]*\) = .*/\1/p' | head -n1)
      _cand=$(printf '%s\n' "$_meta" | sed -n "s/.*candidate versions found which didn't match: \\([^ ,)]*\\).*/\\1/p" | head -n1)
      if [[ -z "$_crate" || -z "$_cand" ]]; then
        break
      fi

      _sum=""
      if [[ -f "$_vendor_lock_root/$_crate/.cargo-checksum.json" ]]; then
        _sum=$(sed -n 's/.*\"package\":\"\\([0-9a-f]*\\)\".*/\\1/p' "$_vendor_lock_root/$_crate/.cargo-checksum.json" | head -n1)
      fi
      msg2 "Adjusting Cargo.lock ${_crate} -> ${_cand}"

      awk -v c="$_crate" -v v="$_cand" -v s="$_sum" '
        BEGIN { in_pkg=0; is_target=0 }
        /^\[\[package\]\]/ { in_pkg=1; is_target=0 }
        in_pkg && $0 == "name = \"" c "\"" { is_target=1 }
        in_pkg && is_target && /^version = / { $0 = "version = \"" v "\"" }
        in_pkg && is_target && s != "" && /^checksum = / { $0 = "checksum = \"" s "\"" }
        { print }
      ' Cargo.lock > Cargo.lock.new && mv Cargo.lock.new Cargo.lock
    done
  ) || true

  # Require llvm-bolt/merge-fdata from current llvm toolchain.
  if ! command -v llvm-bolt >/dev/null 2>&1 || ! command -v merge-fdata >/dev/null 2>&1; then
    echo "Missing llvm-bolt or merge-fdata in PATH"
    return 1
  fi
  local LLVM_PROFDATA_BIN
  LLVM_PROFDATA_BIN="$(command -v llvm-profdata || true)"
  if [[ -z "$LLVM_PROFDATA_BIN" ]]; then
    echo "Missing llvm-profdata in PATH"
    return 1
  fi
  msg2 "✅ BOLT tools ready: $(llvm-bolt --version 2>&1 | head -1 || echo 'not found')"

  # === 1. 环境准备 ===
  if [ ! -x /opt/gcc-git-god/bin/gcc ]; then
    echo "gcc-git-god missing in /opt/gcc-git-god"
    return 1
  fi
  export CC="/opt/gcc-git-god/bin/gcc"
  export CXX="/opt/gcc-git-god/bin/g++"
  export AR="/opt/gcc-git-god/bin/gcc-ar"
  export NM="/opt/gcc-git-god/bin/gcc-nm"
  export RANLIB="/opt/gcc-git-god/bin/gcc-ranlib"
  export CFLAGS="-O3 -march=alderlake -flto=auto -pipe"
  export CXXFLAGS="$CFLAGS"
  export LDFLAGS="-L/opt/gcc-git-god/lib64 -Wl,-rpath,/opt/gcc-git-god/lib64 -flto=auto -Wl,--emit-relocs"
  export LD_LIBRARY_PATH="/opt/gcc-git-god/lib64:$LD_LIBRARY_PATH"
  export CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_LINKER="/opt/gcc-git-god/bin/gcc"
  export CARGO_NET_OFFLINE=true

  # === 2. Stage 1: PGO 插桩编译 ===
  msg2 "🚀 Starting PGO Stage 1: Instrumentation..."
  rm -rf build-pgo
  export RUSTFLAGS="-Cprofile-generate=$srcdir/pgo_data -Ctarget-cpu=alderlake -Copt-level=3 -Clink-arg=-Wl,-rpath,/opt/gcc-git-god/lib64 -Clink-arg=-L/opt/gcc-git-god/lib64 -Clink-arg=-Wl,--emit-relocs -Clinker=/opt/gcc-git-god/bin/gcc"

  cmake -B build-pgo -S fish-shell -G Ninja \
    -DCMAKE_INSTALL_PREFIX=/usr \
    -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
    -DCMAKE_BUILD_TYPE=Release \
    -DWITH_MESSAGE_LOCALIZATION=OFF
  cmake --build build-pgo

  # === 3. Stage 2: PGO Training ===
  msg2 "🏋️ Training Fish (PGO)..."
  export LLVM_PROFILE_FILE="$srcdir/pgo_data/fish-%p-%m.profraw"
  echo 'for i in (seq 1 5000); string match -r ".*" "test_$i" >/dev/null; end' > train.fish

  for i in {1..20}; do
    ./build-pgo/fish --no-config train.fish > /dev/null 2>&1 || true
    ./build-pgo/fish --no-config -c "complete -C 'git comm'" > /dev/null 2>&1 || true
  done

  # === 4. Stage 3: PGO Use + Fat LTO ===
  msg2 "🔥 Starting Stage 3: Extreme Compilation..."
  # Use llvm-profdata from active llvm toolchain.
  "$LLVM_PROFDATA_BIN" merge -output="$srcdir/fish.profdata" "$srcdir/pgo_data"

  export RUSTFLAGS="-Cprofile-use=$srcdir/fish.profdata -Ctarget-cpu=alderlake -Copt-level=3 -Clto=fat -Ccodegen-units=1 -Cpanic=abort -Clink-arg=-Wl,-rpath,/opt/gcc-git-god/lib64 -Clink-arg=-L/opt/gcc-git-god/lib64 -Clink-arg=-Wl,--emit-relocs -Clinker=/opt/gcc-git-god/bin/gcc"
  rm -rf build-pgo

  cmake -B build-pgo -S fish-shell -G Ninja \
    -DCMAKE_INSTALL_PREFIX=/usr \
    -DCMAKE_TRY_COMPILE_TARGET_TYPE=STATIC_LIBRARY \
    -DCMAKE_BUILD_TYPE=Release \
    -DWITH_MESSAGE_LOCALIZATION=OFF
  cmake --build build-pgo

  # === 5. Stage 4: BOLT Optimization ===
  msg2 "⚡ Starting BOLT Optimization..."

  # Create instrumented binary
  msg2 "Creating BOLT instrumented binary..."
  if ! llvm-bolt build-pgo/fish -o fish.inst \
    -instrument \
    -instrumentation-file="$srcdir/bolt_data/fish.fdata" \
    -instrumentation-file-append-pid; then
    msg2 "⚠️ BOLT instrumentation failed, skipping BOLT"
    return 0
  fi

  # Run training
  msg2 "Training BOLT instrumented binary..."
  ./fish.inst --no-config -c 'echo "BOLT training"' 2>/dev/null || true
  ./fish.inst --no-config -c 'for i in (seq 1 100); echo $i >/dev/null; end' 2>/dev/null || true
  ./fish.inst --no-config -c 'string match -r ".*" "hello world"' 2>/dev/null || true
  ./fish.inst --no-config train.fish 2>/dev/null || true
  
  # Merge profile data
  if ls "$srcdir/bolt_data"/fish.fdata.* &>/dev/null; then
    merge-fdata "$srcdir/bolt_data"/fish.fdata.* > "$srcdir/bolt_data/fish.fdata.merged"
    mv "$srcdir/bolt_data/fish.fdata.merged" "$srcdir/bolt_data/fish.fdata"
  fi

  # Apply BOLT
  if [[ -s "$srcdir/bolt_data/fish.fdata" ]]; then
    msg2 "Applying BOLT optimizations..."
    if llvm-bolt build-pgo/fish -o fish.bolt \
      -data="$srcdir/bolt_data/fish.fdata" \
      -reorder-blocks=ext-tsp \
      -reorder-functions=hfsort \
      -split-functions \
      -split-all-cold \
      -icf=1 \
      -dyno-stats; then
      mv fish.bolt build-pgo/fish
      msg2 "✅ BOLT optimization complete!"
    else
      msg2 "⚠️ BOLT optimization failed, using PGO binary"
    fi
  else
    msg2 "⚠️ No BOLT profile data, skipping"
  fi
}

package() {
  DESTDIR="$pkgdir" cmake --install build-pgo
}
openSUSE Build Service is sponsored by