Repository navigation
ci: reuse the normal build for an advisory ABI/API check, published safely #2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
napetrov
wants to merge
46
commits into
master
Choose a base branch
from
abicheck-shadow-integration-clean
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 10 commits
Commits
Show all changes
46 commits
Select commit
Hold shift + click to select a range
8bf1823
ci: add ABICheck shadow ABI scan
napetrov 0b98079
ci: require comparable ABICheck shadow evidence
napetrov 24702cc
ci: install CastXML from conda-forge
napetrov 3201533
ci: collect target-specific ABICheck evidence
napetrov 60f7603
ci: retain ABICheck target failures
napetrov d95ba93
ci: record pvxs C++ dialect in scan evidence
napetrov 8fe7884
ci: override CastXML dialect for GCC 13
napetrov 2a26a19
ci: allow complete pvxs shadow scans
napetrov 839b511
ci: stage PVXS public headers for shadow ABI scan
napetrov d2d4c61
ci: harden PVXS ABICheck shadow integration
napetrov 3825f0d
ci: fail closed on invalid ABI evidence
napetrov d0b31ba
ci: separate public and generated PVXS headers
napetrov c53cc09
ci: stage declared PVXS header sources
napetrov b8a557d
ci: escape ABI summary references
napetrov f4f576b
ci: fail closed when staging ABI reports
napetrov c4de3ae
ci: use L2 ABI scan in shadow workflow
napetrov e955819
ci: update ABICheck scanner to latest main
napetrov 07c17aa
ci: use current ABICheck export syntax
napetrov d1023b8
ci: refresh ABICheck main pin
napetrov a5c52a6
ci: correct ABICheck main revision pin
napetrov 6b096a1
ci: reuse the normal build for the ABI check and publish it safely
napetrov f1cc10a
ci: read the snapshot path from the manifest's own snapshot field
napetrov 8ffffa8
ci: fix baseline eligibility, report identity and the incomplete cont…
napetrov 3732bc9
ci: drop the redundant ABI capture failure marker
napetrov 2f9c269
ci: make the explicit baseline overwrite path actually work
napetrov 0beca2b
ci: keep per-target detail reachable, and pin the reviewed publisher
napetrov 51b33f0
ci: repair the workflow file broken by an invalid env context
napetrov 440fa8e
ci: order the sticky comment by the producer run, and refresh the pin
napetrov 65d3f8d
ci: pin the whole integration to the merged abicheck revision
claude d2c3904
ci: hand the generic ABI machinery back to abicheck
claude 22ca9d5
Merge remote-tracking branch 'origin/master' into abicheck-shadow-int…
claude 7143f5d
docs: record the first real-runner validation of the migrated Actions
claude b13f69d
ci: fix four defects the review found in the migrated integration
claude d97e677
ci: repin to pick up the report fix that made the ordering guard work
claude a4965ce
docs: record why the upstream baseline publisher does not fit yet
claude ea4f758
ci: enforce the default-branch restriction the bootstrap only claimed
claude e27eb5f
docs: state that no real baseline comparison has run on this branch
claude 08b895b
ci: verify a baseline-set's revision before publishing it under a tag
claude 5a129fa
ci: bind the spec as JSON, verify the analysed commit, and say what ran
claude f5ade4a
ci: retest extraction on the CI toolchain, and actually compare somet…
claude 40c8eb8
ci: drop a renderer step that cannot load, and record why
claude bc8e0a1
Merge master into abicheck integration branch
napetrov e4c5428
ci: consolidate the abicheck integration onto supported owners
napetrov daffed4
ci: declare the expected checks independently of comparison execution
napetrov 7cbd1c3
ci: stop pre-creating the baseline channel directories
napetrov d4023bf
ci: repin abicheck to current main and drop default-restating inputs
napetrov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| #!/bin/sh | ||
| # Advisory ABICheck scan. abi-diff.sh / ABICC remains authoritative. | ||
| set -eu | ||
|
|
||
| OLD_REF=${1:-} | ||
| NEW_REF=${2:-HEAD} | ||
| ABICHECK=${ABICHECK:-abicheck} | ||
| JOBS=${ABICHECK_MAKE_JOBS:-2} | ||
| REPORT_ROOT=${ABICHECK_REPORT_ROOT:-compat_reports/abicheck} | ||
| RUN_ROOT=${RUNNER_TEMP:-${TMPDIR:-/tmp}}/pvxs-abicheck-${GITHUB_RUN_ID:-$$} | ||
|
|
||
| explicit_old=1 | ||
| new_sha=$(git rev-parse "$NEW_REF^{commit}") | ||
| if [ -z "$OLD_REF" ]; then | ||
| explicit_old=0 | ||
| OLD_REF=$(git describe --tags --abbrev=0 "$new_sha") | ||
| fi | ||
| old_sha=$(git rev-parse "$OLD_REF^{commit}") | ||
| if [ "$explicit_old" -eq 0 ] && [ "$old_sha" = "$new_sha" ]; then | ||
| OLD_REF=$(git describe --tags --abbrev=0 "$new_sha^") | ||
| old_sha=$(git rev-parse "$OLD_REF^{commit}") | ||
| fi | ||
|
|
||
| mkdir -p "$RUN_ROOT" "$REPORT_ROOT" | ||
| export HOME="$RUN_ROOT/home" | ||
| export XDG_CACHE_HOME="$RUN_ROOT/cache" | ||
| export TMPDIR="$RUN_ROOT/tmp" | ||
| mkdir -p "$HOME" "$XDG_CACHE_HOME" "$TMPDIR" | ||
|
|
||
| EPICS_BASE=${EPICS_BASE:-} | ||
| if [ -z "$EPICS_BASE" ] && [ -f configure/RELEASE.local ]; then | ||
| EPICS_BASE=$(sed -n -E 's/^[[:space:]]*EPICS_BASE[[:space:]]*=[[:space:]]*//p' configure/RELEASE.local | tail -n 1) | ||
| fi | ||
| [ -n "$EPICS_BASE" ] && [ -d "$EPICS_BASE/include" ] || { | ||
| echo "EPICS_BASE include tree unavailable; use cue.py exec after prepare" >&2 | ||
| exit 64 | ||
| } | ||
| command -v bear >/dev/null || { echo "bear is required for complete build evidence" >&2; exit 64; } | ||
|
|
||
| OLD_SRC="$RUN_ROOT/old" | ||
| NEW_SRC="$RUN_ROOT/new" | ||
| # git archive has no enclosing directory; extract each revision into its own | ||
| # fixed root so paths and build evidence remain side-specific. | ||
| mkdir -p "$OLD_SRC" "$NEW_SRC" | ||
| git archive "$old_sha" | tar -C "$OLD_SRC" -xf - | ||
| git archive "$new_sha" | tar -C "$NEW_SRC" -xf - | ||
|
|
||
| prepare_build() { | ||
| src=$1 | ||
| [ -f configure/RELEASE.local ] && cp configure/RELEASE.local "$src/configure/" | ||
| [ -f configure/CONFIG_SITE.local ] && cp configure/CONFIG_SITE.local "$src/configure/" | ||
| sed -i -e "s|\$(TOP)|$(pwd)|g" -e 's|-Werror||g' "$src"/configure/*.local 2>/dev/null || true | ||
| bear --output "$src/compile_commands.json" -- \ | ||
| make -C "$src" CROSS_COMPILER_TARGET_ARCHS= OPT_CFLAGS='-g -Og' OPT_CXXFLAGS='-g -Og' ioc -j"$JOBS" | ||
| } | ||
|
|
||
| prepare_build "$OLD_SRC" | ||
| prepare_build "$NEW_SRC" | ||
|
|
||
| stage_headers() { | ||
| src=$1 | ||
| target=$2 | ||
| out=$3 | ||
| mkdir -p "$out/pvxs" | ||
| if [ "$target" = libpvxs ]; then | ||
| awk '/^INC[[:space:]]*\+=[[:space:]]*pvxs\// {print $3}' "$src/src/Makefile" | while read -r header; do | ||
| header_src="$src/src/$header" | ||
| [ "$header" != pvxs/versionNum.h ] || header_src="$src/src/O.Common/$header" | ||
| [ -f "$header_src" ] || { echo "missing public header $header" >&2; exit 64; } | ||
| mkdir -p "$out/$(dirname "$header")" | ||
| cp "$header_src" "$out/$header" | ||
| done | ||
| else | ||
| cp "$src/ioc/pvxs/iochooks.h" "$out/pvxs/iochooks.h" | ||
| fi | ||
| } | ||
|
|
||
| project_compile_db() { | ||
| src=$1 | ||
| target=$2 | ||
| out=$3 | ||
| python3 - "$src/compile_commands.json" "$src" "$target" "$out" <<'PY' | ||
| import json, pathlib, sys | ||
| entries = json.load(open(sys.argv[1])) | ||
| source_root = pathlib.Path(sys.argv[2]).resolve() | ||
| target, out = sys.argv[3:] | ||
| component_root = (source_root / ('src' if target == 'libpvxs' else 'ioc')).resolve() | ||
| selected = [] | ||
| for entry in entries: | ||
| source = pathlib.Path(entry['file']) | ||
| if not source.is_absolute(): | ||
| source = pathlib.Path(entry.get('directory') or source_root) / source | ||
| try: | ||
| source.resolve().relative_to(component_root) | ||
| except ValueError: | ||
| continue | ||
| selected.append(entry) | ||
| if not selected: | ||
| raise SystemExit(f'no compile commands selected for {target}') | ||
| json.dump(selected, open(out, 'w'), indent=2) | ||
| PY | ||
| } | ||
|
|
||
| find_dso() { | ||
| matches=$(find "$1/lib" -type f -name "$2.so.*" -print | LC_ALL=C sort) | ||
| count=$(printf '%s\n' "$matches" | sed '/^$/d' | wc -l) | ||
| [ "$count" -eq 1 ] || { | ||
| echo "expected one $2 DSO below $1/lib, found $count" >&2 | ||
| return 64 | ||
| } | ||
| printf '%s\n' "$matches" | ||
| } | ||
|
|
||
| old_id=$(printf '%s' "$old_sha" | cut -c1-12) | ||
| new_id=$(printf '%s' "$new_sha" | cut -c1-12) | ||
| status_file="$REPORT_ROOT/summary.json" | ||
| printf '{"old_ref":"%s","old_sha":"%s","new_ref":"%s","new_sha":"%s","targets":[' \ | ||
| "$OLD_REF" "$old_sha" "$NEW_REF" "$new_sha" > "$status_file" | ||
| first=1 | ||
| overall=0 | ||
|
|
||
| run_one() { | ||
| target=$1 | ||
| oldso=$(find_dso "$OLD_SRC" "$target") | ||
| newso=$(find_dso "$NEW_SRC" "$target") | ||
| [ -n "$oldso" ] && [ -n "$newso" ] || return 64 | ||
| old_headers="$RUN_ROOT/headers-old-$target" | ||
| new_headers="$RUN_ROOT/headers-new-$target" | ||
| stage_headers "$OLD_SRC" "$target" "$old_headers" || return $? | ||
| stage_headers "$NEW_SRC" "$target" "$new_headers" || return $? | ||
| old_db="$OLD_SRC/compile_commands.$target.json" | ||
| new_db="$NEW_SRC/compile_commands.$target.json" | ||
| project_compile_db "$OLD_SRC" "$target" "$old_db" || return $? | ||
| project_compile_db "$NEW_SRC" "$target" "$new_db" || return $? | ||
| base="$REPORT_ROOT/${target}_${old_id}_to_${new_id}" | ||
| if "$ABICHECK" compare "$oldso" "$newso" \ | ||
| --version "old=$old_sha" --version "new=$new_sha" \ | ||
| --header "old=$old_headers" --header "new=$new_headers" \ | ||
| --include "old:pvxs=$old_headers" --include "new:pvxs=$new_headers" \ | ||
| --include "old:epics=$EPICS_BASE/include" --include "new:epics=$EPICS_BASE/include" \ | ||
| --include "old:epics-os=$EPICS_BASE/include/os/Linux" --include "new:epics-os=$EPICS_BASE/include/os/Linux" \ | ||
| --include "old:epics-gcc=$EPICS_BASE/include/compiler/gcc" --include "new:epics-gcc=$EPICS_BASE/include/compiler/gcc" \ | ||
| --depth source --sources "old=$OLD_SRC" --sources "new=$NEW_SRC" \ | ||
| --build-info "old=$old_db" --build-info "new=$new_db" \ | ||
| --config "$PWD/.ci-local/abicheck.yml" \ | ||
| --format review --write "json=$base.json" -o "$base.md" | ||
| then | ||
| rc=0 | ||
| else | ||
| rc=$? | ||
| fi | ||
| if [ ! -s "$base.json" ] || [ ! -s "$base.md" ]; then | ||
| echo "missing comparison reports for $target" >&2 | ||
| rc=64 | ||
| elif ! python3 - "$base.json" <<'PY' | ||
| import json, sys | ||
| report = json.load(open(sys.argv[1])) | ||
| if report.get("analysis_assurance_exit_contribution") not in (0, None): | ||
| raise SystemExit("analysis assurance is incomplete") | ||
| PY | ||
| then | ||
| echo "incomplete analysis assurance for $target" >&2 | ||
| rc=1 | ||
| fi | ||
| printf '%s\n' "$rc" > "$RUN_ROOT/$target.exit-code" | ||
| if [ -n "${GITHUB_STEP_SUMMARY:-}" ] && [ -f "$base.md" ]; then cat "$base.md" >> "$GITHUB_STEP_SUMMARY"; fi | ||
| case "$rc" in 0|2|4) return 0;; *) return "$rc";; esac | ||
| } | ||
|
|
||
| append_target() { | ||
| target=$1 | ||
| rc=$2 | ||
| base="$REPORT_ROOT/${target}_${old_id}_to_${new_id}" | ||
| [ "$first" -eq 1 ] || printf ',' >> "$status_file" | ||
| first=0 | ||
| if [ -f "$base.json" ]; then | ||
| printf '{"target":"%s","exit_code":%s,"report":"%s.json"}' "$target" "$rc" "$(basename "$base")" >> "$status_file" | ||
| else | ||
| printf '{"target":"%s","exit_code":%s,"report":null}' "$target" "$rc" >> "$status_file" | ||
| fi | ||
| } | ||
|
|
||
| for target in libpvxs libpvxsIoc; do | ||
| if run_one "$target"; then | ||
| rc=0 | ||
| else | ||
| rc=$? | ||
| fi | ||
| if [ -f "$RUN_ROOT/$target.exit-code" ]; then | ||
| rc=$(cat "$RUN_ROOT/$target.exit-code") | ||
| fi | ||
| append_target "$target" "$rc" | ||
| case "$rc" in 0|2|4) ;; *) overall=1;; esac | ||
| done | ||
| printf '],"integration_health":%s}\n' "$overall" >> "$status_file" | ||
| exit "$overall" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| # CastXML packaged on the GitHub runner cannot parse libstdc++ with PVXS's | ||
| # default C++11 mode. This is deliberately recorded as extraction context; | ||
| # the libraries themselves keep PVXS's normal build flags. | ||
| compile: | ||
| options: | ||
| - -std=c++17 | ||
| assurance: | ||
| require_complete: true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,78 @@ | ||
| name: ABICheck shadow ABI scan | ||
|
|
||
| on: | ||
| pull_request: | ||
| push: | ||
| branches: [master] | ||
| tags: ['*'] | ||
| workflow_dispatch: | ||
|
|
||
| concurrency: | ||
| group: abicheck-shadow-${{ github.workflow }}-${{ github.ref }} | ||
| cancel-in-progress: true | ||
|
|
||
| # The scanner is an advisory, parallel signal. It needs no PR comments, no | ||
| # status write access, and no repository secrets. | ||
| permissions: | ||
| contents: read | ||
|
|
||
| jobs: | ||
| abicheck-shadow: | ||
| runs-on: ubuntu-latest | ||
| # A full source-evidence scan runs two revisions of both public DSOs. | ||
| # Keep this above the measured ~100-minute worst case, not at 45 minutes. | ||
| timeout-minutes: 120 | ||
| env: | ||
| SETUP_PATH: .ci-local | ||
| SET: defaults | ||
| CMP: gcc | ||
| BCFG: default | ||
| BASE: "7.0" | ||
| _PVXS_ABORT_ON_CRIT: 1 | ||
| PVXS_LOG: pvxs.*=WARN | ||
| ABICHECK_MAKE_JOBS: "2" | ||
| ABICHECK_REF: 0ab7da2eba3016bcad86657fb74239c66ef3ffeb | ||
|
|
||
| steps: | ||
| - uses: actions/checkout@v6 | ||
| with: | ||
| fetch-depth: "0" | ||
| submodules: true | ||
| persist-credentials: false | ||
|
|
||
| - name: Install scanner tools | ||
| shell: bash | ||
| run: | | ||
| set -euxo pipefail | ||
| sudo apt-get update | ||
| sudo apt-get -y install libreadline-dev libevent-dev cmake bear | ||
| /usr/share/miniconda/bin/conda install -y -c conda-forge castxml | ||
| VENV="$RUNNER_TEMP/abicheck-venv" | ||
| python -m venv "$VENV" | ||
| "$VENV/bin/pip" install --disable-pip-version-check \ | ||
| "git+https://github.com/abicheck/abicheck@${ABICHECK_REF}" | ||
| echo "/usr/share/miniconda/bin" >> "$GITHUB_PATH" | ||
| echo "$VENV/bin" >> "$GITHUB_PATH" | ||
| echo "ABICHECK=$VENV/bin/abicheck" >> "$GITHUB_ENV" | ||
|
|
||
| - name: Verify scanner tools | ||
| shell: bash | ||
| run: | | ||
| set -euxo pipefail | ||
| castxml --version | ||
| "$ABICHECK" --version | ||
|
|
||
| - name: Prepare EPICS dependencies | ||
| run: python .ci/cue.py prepare | ||
|
|
||
| - name: Run full source-evidence shadow scan | ||
| run: python .ci/cue.py exec ./.ci-local/abicheck-diff.sh | ||
|
|
||
| - name: Upload ABICheck reports | ||
| if: ${{ always() }} | ||
| uses: actions/upload-artifact@v7 | ||
| with: | ||
| retention-days: 30 | ||
| name: abicheck-shadow | ||
| path: compat_reports/abicheck | ||
| if-no-files-found: error |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.