From 6187909c0e6db631dc63a229c5664d29edf995c5 Mon Sep 17 00:00:00 2001 From: Guillaume Lagrange Date: Mon, 8 Jun 2026 14:58:56 +0200 Subject: [PATCH 1/3] chore: add helper script to use local versions of samply and framehop easily --- scripts/samply-dev.sh | 367 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 367 insertions(+) create mode 100755 scripts/samply-dev.sh diff --git a/scripts/samply-dev.sh b/scripts/samply-dev.sh new file mode 100755 index 00000000..051007c5 --- /dev/null +++ b/scripts/samply-dev.sh @@ -0,0 +1,367 @@ +#!/bin/sh +# Toggle "samply dev mode" for the runner. +# +# Dev mode redirects the runner's `samply`, `framehop`, and +# `linux-perf-event-reader` dependencies to local sibling checkouts via +# `.cargo/config.toml` patch files, without ever touching the committed +# `Cargo.toml` / `Cargo.lock` / `.gitignore`. This lets you iterate on all +# three crates in place and have the runner pick the changes up immediately. +# +# Redirected dependencies (all resolve to siblings of the runner repo): +# - samply (../samply-codspeed) committed as a git dep in the runner's +# Cargo.toml; patched via [patch.""] +# - framehop (../framehop) committed as a git dep in samply's +# Cargo.toml; patched via [patch.""] +# - linux-perf-event-reader (../linux-perf-event-reader) +# a crates.io dep pulled in transitively via +# linux-perf-data, so it is overridden with +# [patch.crates-io] rather than a git-url patch +# +# The patch files are kept out of git locally via each repo's +# `.git/info/exclude` (a per-clone, uncommitted ignore list) — nothing is added +# to the tracked `.gitignore`. +# +# Building with the patch in place rewrites the tracked `Cargo.lock` (the +# git-rev `source` lines are dropped for path deps). `.git/info/exclude` only +# hides untracked files, so the lock is instead masked with +# `git update-index --skip-worktree`, which tells git to ignore local edits to +# a tracked file. `off` clears the flag and restores the lock to HEAD. +# +# Two patch files are managed (both locally excluded): +# - /.cargo/config.toml patches samply + framehop + reader -> local +# - /.cargo/config.toml patches framehop + reader -> local (so samply +# standalone builds also use them) +# +# The committed manifests stay pinned to git revs, so all repos remain +# committable at any time. Each repo's tracked revision is read straight from +# the committed manifests (the `rev = "..."` in the runner's and samply's +# Cargo.toml) — revisions are never hardcoded in this script. `sync` checks out +# those tracked revisions in each local checkout, skipping any checkout that has +# uncommitted changes so in-progress work is never clobbered. Bumping a rev for +# release is a separate, manual step. +# +# Every command ends by printing a recap of which local checkout each +# dependency resolves to, its current HEAD, the manifest-tracked revision, and +# whether the two agree. +# +# Usage: +# scripts/samply-dev.sh on enable dev mode (write patch files) +# scripts/samply-dev.sh off disable dev mode (remove patch files) +# scripts/samply-dev.sh status show current state +# scripts/samply-dev.sh sync check out each repo's manifest-tracked revision +set -eu + +# Resolve repo roots relative to this script, not the cwd. +RUNNER_ROOT=$(CDPATH= cd -- "$(dirname -- "$0")/.." && pwd) +SAMPLY_ROOT=$(CDPATH= cd -- "$RUNNER_ROOT/../samply-codspeed" 2>/dev/null && pwd || true) +FRAMEHOP_ROOT=$(CDPATH= cd -- "$RUNNER_ROOT/../framehop" 2>/dev/null && pwd || true) +READER_ROOT=$(CDPATH= cd -- "$RUNNER_ROOT/../linux-perf-event-reader" 2>/dev/null && pwd || true) + +SAMPLY_URL="https://github.com/CodSpeedHQ/samply-codspeed" +FRAMEHOP_URL="https://github.com/CodSpeedHQ/framehop" +READER_URL="https://github.com/AvalancheHQ/linux-perf-event-reader" + +RUNNER_CONFIG="$RUNNER_ROOT/.cargo/config.toml" +SAMPLY_CONFIG="$SAMPLY_ROOT/.cargo/config.toml" + +# Manifests that pin each repo's tracked git revision (single source of truth; +# revs are never hardcoded in this script). `sync` reads `rev = "..."` from them. +# framehop -> a git dep in samply's Cargo.toml +# reader -> a [patch.crates-io] git entry in samply's Cargo.toml +# (linux-perf-event-reader comes from crates.io transitively via +# linux-perf-data, so it is overridden with [patch.crates-io], not a +# git-url patch) +# samply -> a git dep in the runner's Cargo.toml +SAMPLY_MANIFEST="$SAMPLY_ROOT/samply/Cargo.toml" +RUNNER_MANIFEST="$RUNNER_ROOT/Cargo.toml" + +# Extract the `rev = "..."` from the first manifest line mentioning $2 (a repo +# URL slug). Empty if the manifest or the entry is absent. +# manifest_rev +manifest_rev() { + manifest=$1 + needle=$2 + [ -f "$manifest" ] || return 0 + grep -E "$needle" "$manifest" 2>/dev/null \ + | grep -oE 'rev[[:space:]]*=[[:space:]]*"[0-9a-fA-F]{7,40}"' \ + | grep -oE '[0-9a-fA-F]{7,40}' \ + | head -1 \ + || true +} + +framehop_tracked_rev() { manifest_rev "$SAMPLY_MANIFEST" 'CodSpeedHQ/framehop'; } +reader_tracked_rev() { manifest_rev "$SAMPLY_MANIFEST" 'CodSpeedHQ/linux-perf-event-reader'; } +samply_tracked_rev() { manifest_rev "$RUNNER_MANIFEST" 'CodSpeedHQ/samply-codspeed'; } + +# Pattern stored in .git/info/exclude (relative to each repo root). +EXCLUDE_ENTRY="/.cargo/config.toml" + +usage() { + echo "Usage: $0 {on|off|status|sync}" >&2 + echo " on enable dev mode (write patch files)" >&2 + echo " off disable dev mode (remove patch files)" >&2 + echo " status show dev-mode state" >&2 + echo " sync check out each repo's manifest-tracked revision" >&2 + echo "(every command ends by printing the repo-wiring recap)" >&2 + exit 2 +} + +# Resolve /.git/info/exclude, failing if $1 is not a git checkout. +resolve_exclude_file() { + repo_root=$1 + git_dir=$(CDPATH= cd -- "$repo_root" && git rev-parse --git-dir 2>/dev/null) || { + echo "error: $repo_root is not a git repository" >&2 + exit 1 + } + case "$git_dir" in + /*) ;; # already absolute + *) git_dir="$repo_root/$git_dir" ;; + esac + printf '%s\n' "$git_dir/info/exclude" +} + +# Ensure $EXCLUDE_ENTRY is present in /.git/info/exclude. +add_local_exclude() { + exclude_file=$(resolve_exclude_file "$1") + mkdir -p "$(dirname -- "$exclude_file")" + if [ ! -f "$exclude_file" ] || ! grep -qxF "$EXCLUDE_ENTRY" "$exclude_file"; then + printf '%s\n' "$EXCLUDE_ENTRY" >> "$exclude_file" + fi +} + +# Remove $EXCLUDE_ENTRY from /.git/info/exclude if present. +remove_local_exclude() { + exclude_file=$(resolve_exclude_file "$1") + [ -f "$exclude_file" ] || return 0 + if grep -qxF "$EXCLUDE_ENTRY" "$exclude_file"; then + grep -vxF "$EXCLUDE_ENTRY" "$exclude_file" > "$exclude_file.tmp" && mv "$exclude_file.tmp" "$exclude_file" + fi +} + +# Restore Cargo.lock to HEAD, then mask the local build-induced edits to it. +mask_cargo_lock() { + repo_root=$1 + git -C "$repo_root" checkout -- Cargo.lock + git -C "$repo_root" update-index --skip-worktree Cargo.lock +} + +# Unmask Cargo.lock and restore it to HEAD. +unmask_cargo_lock() { + repo_root=$1 + git -C "$repo_root" update-index --no-skip-worktree Cargo.lock 2>/dev/null || true + git -C "$repo_root" checkout -- Cargo.lock 2>/dev/null || true +} + +require_dirs() { + if [ -z "$SAMPLY_ROOT" ]; then + echo "error: ../samply-codspeed not found next to the runner repo" >&2 + exit 1 + fi + if [ -z "$FRAMEHOP_ROOT" ]; then + echo "error: ../framehop not found next to the runner repo" >&2 + exit 1 + fi + if [ -z "$READER_ROOT" ]; then + echo "error: ../linux-perf-event-reader not found next to the runner repo" >&2 + exit 1 + fi +} + +enable() { + require_dirs + + add_local_exclude "$RUNNER_ROOT" + mkdir -p "$RUNNER_ROOT/.cargo" + cat > "$RUNNER_CONFIG" < "$SAMPLY_CONFIG" </dev/null || true + [ -n "$SAMPLY_ROOT" ] && rmdir "$SAMPLY_ROOT/.cargo" 2>/dev/null || true + + # Drop the local-exclude entries we added. + remove_local_exclude "$RUNNER_ROOT" + [ -n "$SAMPLY_ROOT" ] && remove_local_exclude "$SAMPLY_ROOT" + + if [ "$removed" -eq 1 ]; then + echo "samply dev mode: OFF" + else + echo "samply dev mode: already OFF" + fi +} + +# Current HEAD of a checkout (short), or "-" if missing/not a repo. +repo_head() { + repo_root=$1 + [ -n "$repo_root" ] || { printf '%s' "-"; return; } + git -C "$repo_root" rev-parse --short HEAD 2>/dev/null || printf '%s' "-" +} + +# Collapse a leading $HOME to ~ to keep paths short. +tilde() { + case "$1" in + "$HOME"/*) printf '~%s' "${1#"$HOME"}" ;; + "$HOME") printf '~' ;; + *) printf '%s' "$1" ;; + esac +} + +# Shorten a git rev to 12 chars for display (enough to identify; sync is still +# computed from the full value). "-" / empty pass through unchanged. +short_rev() { + case "$1" in + "" | "-") printf '%s' "${1:--}" ;; + *) printf '%s' "$1" | cut -c1-12 ;; + esac +} + +# Print a recap block for one dependency: a status line (glyph + label + state) +# followed by aligned path / head / tracked detail lines. +# recap_line