Skip to content

🔄 synced file(s) with halotukozak-com/.github #64

🔄 synced file(s) with halotukozak-com/.github

🔄 synced file(s) with halotukozak-com/.github #64

Workflow file for this run

name: Binary compatibility
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
permissions:
contents: read
pull-requests: write
# Report-only: pre-1.0, and the check itself is a prototype. It never fails
# the build — instead, on a PR, it manages a `needs-major` label (binary
# incompatibility means an eventual major bump) so the break is visible and
# trackable without blocking. The label is removed again once a later push
# to the same PR is compatible.
#
# On a PR, the baseline is the current `main` tip (the merge commit's first
# parent), not the last release — otherwise a break introduced by PR n stays
# baked into `main` and every unrelated PR n+1, n+2, ... would keep getting
# relabeled until the next release, even though they changed nothing binary.
# On a plain push (e.g. a merge to main), there's no PR to label, so the
# report falls back to comparing against the last release for the summary.
#
# There's no `needs-minor` (source/TASTy-compat-only breaks) yet: TASTy-MiMa
# can't read Scala 3.9's TASTy format yet (tasty-mima lags the compiler).
# Add it here once that's unblocked.
jobs:
mima:
name: MiMa (binary)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
fetch-depth: 0
- name: Setup coursier cache
uses: coursier/cache-action@v8.1
- uses: VirtusLab/scala-cli-setup@v1
- name: Check binary compatibility
id: mima
run: |
set -euo pipefail
org=$(grep -oP '(?<=using publish.organization ).*' project.scala | tr -d '"')
name=$(grep -oP '(?<=using publish.name ).*' project.scala | tr -d '"')
scala-cli --power package . --exclude benchmark --library -o "$RUNNER_TEMP/new.jar" --force
shared_cp=$(scala-cli --power compile . --exclude benchmark --print-class-path | tail -1)
if [ "${{ github.event_name }}" = "pull_request" ]; then
base_sha=$(git rev-parse HEAD^1)
baseline_label="main@${base_sha:0:7}"
echo "Baseline: $baseline_label (does this PR itself introduce a new binary incompatibility?)"
git worktree add --detach --quiet "$RUNNER_TEMP/old-src" "$base_sha"
scala-cli --power package "$RUNNER_TEMP/old-src" --exclude benchmark --library -o "$RUNNER_TEMP/old.jar" --force
old_jar="$RUNNER_TEMP/old.jar"
else
base_tag=$(git tag -l 'v*' --sort=-v:refname | head -1)
if [ -z "$base_tag" ]; then
echo "No release tag yet — nothing to compare against." >> "$GITHUB_STEP_SUMMARY"
echo "broken=false" >> "$GITHUB_OUTPUT"
exit 0
fi
base_version=${base_tag#v}
baseline_label="$base_version"
echo "Baseline: $org:${name}_3:$base_version"
printf '' > "$RUNNER_TEMP/empty.scala"
old_cp=$(scala-cli --power compile "$RUNNER_TEMP/empty.scala" --dependency "$org::$name:$base_version" --print-class-path | tail -1)
old_jar=$(printf '%s' "$old_cp" | tr ':' '\n' | grep "/${name}_3/$base_version/")
fi
echo "baseline=$baseline_label" >> "$GITHUB_OUTPUT"
set +e
scala-cli run .mima/bin-compat-check.scala -- "$old_jar" "$RUNNER_TEMP/new.jar" "$shared_cp" | tee "$RUNNER_TEMP/mima-out.txt"
status=$?
set -e
if [ $status -ne 0 ]; then
{
echo "### MiMa found binary incompatibilities vs \`$baseline_label\`"
echo '```'
cat "$RUNNER_TEMP/mima-out.txt"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
echo "::warning title=Binary compatibility::MiMa found incompatibilities vs $baseline_label — see the job summary"
echo "broken=true" >> "$GITHUB_OUTPUT"
else
echo "Binary compatible with \`$baseline_label\`." >> "$GITHUB_STEP_SUMMARY"
echo "broken=false" >> "$GITHUB_OUTPUT"
fi
- name: Label needs-major on binary incompatibility
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
if [ "${{ steps.mima.outputs.broken }}" = "true" ]; then
gh label create needs-major --repo "${{ github.repository }}" \
--color B60205 --description "Binary-incompatible change — needs a major version bump" \
--force >/dev/null
gh pr edit "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}" \
--add-label needs-major
else
gh pr edit "${{ github.event.pull_request.number }}" --repo "${{ github.repository }}" \
--remove-label needs-major || true
fi
- name: Post sticky comment with binary compatibility report
if: github.event_name == 'pull_request'
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
marker='<!-- mima-report -->'
baseline='${{ steps.mima.outputs.baseline }}'
repo='${{ github.repository }}'
pr=${{ github.event.pull_request.number }}
comment_id=$(gh api "repos/$repo/issues/$pr/comments" --paginate \
--jq ".[] | select(.body | startswith(\"$marker\")) | .id" | tail -1)
if [ "${{ steps.mima.outputs.broken }}" = "true" ]; then
{
echo "$marker"
echo "### ⚠️ MiMa: binary incompatibility vs \`$baseline\`"
echo
echo "This PR breaks binary compatibility — it will need a major version bump."
echo
echo '<details><summary>Details</summary>'
echo
echo '```'
cat "$RUNNER_TEMP/mima-out.txt"
echo '```'
echo
echo '</details>'
} > "$RUNNER_TEMP/mima-comment.md"
if [ -n "$comment_id" ]; then
gh api --method PATCH "repos/$repo/issues/comments/$comment_id" -F body=@"$RUNNER_TEMP/mima-comment.md" >/dev/null
else
gh api --method POST "repos/$repo/issues/$pr/comments" -F body=@"$RUNNER_TEMP/mima-comment.md" >/dev/null
fi
elif [ -n "$comment_id" ]; then
{
echo "$marker"
echo "### ✅ MiMa: binary compatible with \`$baseline\`"
echo
echo "A later push resolved the binary incompatibility flagged above."
} > "$RUNNER_TEMP/mima-comment.md"
gh api --method PATCH "repos/$repo/issues/comments/$comment_id" -F body=@"$RUNNER_TEMP/mima-comment.md" >/dev/null
fi