Repository navigation
Expand file tree
/
Copy pathaction.yml
More file actions
186 lines (181 loc) · 8.37 KB
/
Copy pathaction.yml
File metadata and controls
186 lines (181 loc) · 8.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
name: solinv diff
description: Fail a PR or push that removes an Anchor account check (has_one, seeds, signer, constraint), annotated on the line; warn on one a native or Pinocchio program's code drops.
branding:
icon: shield
color: purple
inputs:
program-src:
description: >-
Path to the program's source, relative to the repository root
(e.g. programs/my_program/src); several programs, one path per line.
Left empty: every `programs/<name>/src` (at any depth) with a
`#[derive(Accounts)]` in it, and every `src` directory with a native
or Pinocchio entrypoint (`entrypoint!`, `program_entrypoint!`,
`lazy_program_entrypoint!`, or an `extern "C" fn entrypoint`), at the
base or the head.
default: ''
base-ref:
description: >-
Commit to compare against. Defaults to the pull request's base, the
merge queue's base, or on a push the commit the branch was at before it.
default: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }}
solinv-rev:
description: solinv commit to install. Defaults to the ref this action is used at.
default: ''
fail-on-removed:
description: Fail the job when a check is removed ('true' or 'false')
default: 'true'
runs:
using: composite
steps:
- name: Pick the solinv commit
id: rev
shell: bash
env:
INPUT_REV: ${{ inputs.solinv-rev }}
ACTION_REF: ${{ github.action_ref }}
run: |
rev="${INPUT_REV:-$ACTION_REF}"
if [ -z "$rev" ]; then
echo "::error title=solinv::set solinv-rev (no action ref to default to)"
exit 1
fi
# A branch or tag moves; install by the commit it names
# now, so a moved tag never restores an old binary.
if ! [[ "$rev" =~ ^[0-9a-f]{7,40}$ ]]; then
sha=$(git ls-remote https://github.com/psyto/solinv "$rev" "refs/tags/$rev^{}" \
| awk 'END { print $1 }')
if [ -z "$sha" ]; then
echo "::error title=solinv::cannot resolve solinv ref '$rev'"
exit 1
fi
rev="$sha"
fi
echo "rev=$rev" >> "$GITHUB_OUTPUT"
- name: Install solinv
shell: bash
env:
REV: ${{ steps.rev.outputs.rev }}
GH_TOKEN: ${{ github.token }}
run: |
# The release binary built from this exact commit (Linux x64), used
# only with a build attestation that solinv's own release workflow
# built it from this commit. Otherwise, or if anything fails,
# compile. No cache: what a cache restores cannot be trusted.
name="solinv-${REV}-x86_64-unknown-linux-gnu.tar.gz"
if [ "$RUNNER_OS" = Linux ] && [ "$RUNNER_ARCH" = X64 ] && [ ${#REV} = 40 ]; then
url=$(gh api --paginate "repos/psyto/solinv/releases?per_page=100" \
--jq ".[].assets[] | select(.name == \"$name\") | .browser_download_url" 2>/dev/null | head -n1)
if [ -n "$url" ] && (cd "$RUNNER_TEMP" && curl -fsSLO "$url" \
&& gh attestation verify "$name" --repo psyto/solinv \
--signer-workflow psyto/solinv/.github/workflows/release.yml \
--source-digest "$REV" >/dev/null \
&& mkdir -p solinv-bin && tar -C solinv-bin -xzf "$name" \
&& solinv-bin/bin/solinv --version); then
echo "solinv: release binary for $REV, attested"
exit 0
fi
rm -rf "$RUNNER_TEMP/solinv-bin"
echo "solinv: no attested release binary for $REV; compiling"
fi
cargo install --locked --git https://github.com/psyto/solinv --rev "$REV" \
solinv-cli --root "$RUNNER_TEMP/solinv-bin"
- name: Check out the base version
id: base
shell: bash
env:
BASE: ${{ inputs.base-ref }}
run: |
if [ -z "$BASE" ]; then
echo "::error title=solinv::no base-ref (not a pull_request, merge_group or push event? set base-ref)"
exit 1
fi
# The first push of a new branch has no commit before it.
if [[ "$BASE" =~ ^0{40}$ ]]; then
echo "::notice title=solinv::first push of this branch: nothing to compare against"
echo "none=true" >> "$GITHUB_OUTPUT"
exit 0
fi
# A force push can leave the old commit unfetchable: say so, do not pass silently.
if ! git fetch --no-tags --depth=1 origin "$BASE"; then
echo "::error title=solinv::cannot fetch base $BASE (force-pushed away?); set base-ref"
exit 1
fi
git worktree add --detach "$RUNNER_TEMP/solinv-base" "$BASE"
- name: Compare account checks
if: steps.base.outputs.none != 'true'
shell: bash
env:
SRC: ${{ inputs.program-src }}
FAIL: ${{ inputs.fail-on-removed }}
run: |
# Has an accounts struct: read as solinv reads, whole files, so a
# derive may span lines.
anchor() {
find "$1" -name '*.rs' -type f -exec perl -0777 -ne \
'$f = 1 if /#\[derive\([^)]*\bAccounts\b/; END { exit($f ? 0 : 1) }' {} + 2>/dev/null
}
# A native or Pinocchio program: an entrypoint macro. Its checks are
# read from the code; what it drops is a warning, never a failure.
native() {
find "$1" -name '*.rs' -type f -exec perl -0777 -ne \
's#/\*.*?\*/##gs; s#//[^\n]*##g; $f = 1 if /\b(?:lazy_program_entrypoint|program_entrypoint|lazy_entrypoint|entrypoint|entrypoint_no_alloc)\s*!\s*\(|\bextern\s+"C"\s+fn\s+entrypoint\b/; END { exit($f ? 0 : 1) }' {} + 2>/dev/null
}
program() { anchor "$1" || native "$1"; }
if [ -z "${SRC//[[:space:]]/}" ]; then
# Every Anchor program: a `programs/<name>/src` with an accounts
# struct, in either version (one the change emptied counts too).
# And every `src` directory with a native entrypoint.
SRC=$(for root in "$RUNNER_TEMP/solinv-base" .; do
( cd "$root" && git ls-files -- '*programs/*/src/*.rs' \
| sed -E 's#^(.*programs/[^/]+/src)/.*#\1#' | sort -u \
| while IFS= read -r d; do
# `if`, not `&&`: under `set -e` a last non-Anchor dir
# would end the step with status 1 and no word.
if anchor "$d"; then echo "$d"; fi
done
git ls-files -- 'src/*.rs' '*/src/*.rs' \
| sed -E 's#^((.*/)?src)/.*#\1#' | sort -u \
| while IFS= read -r d; do
if native "$d" && ! anchor "$d"; then echo "$d"; fi
done )
done | sort -u)
if [ -z "$SRC" ]; then
echo "::error title=solinv::no program found (a programs/<name>/src with #[derive(Accounts)], or a src with a native entrypoint); set program-src"
exit 1
fi
echo "solinv: comparing $(echo "$SRC" | wc -l | tr -d ' ') program(s):"
echo "$SRC"
fi
flag=""
[ "$FAIL" = "true" ] || flag="--no-fail"
# Every path is compared, even after one fails.
status=0
while IFS= read -r src; do
src="${src#"${src%%[![:space:]]*}"}"
src="${src%"${src##*[![:space:]]}"}"
[ -n "$src" ] || continue
if [ ! -e "$RUNNER_TEMP/solinv-base/$src" ] || ! program "$RUNNER_TEMP/solinv-base/$src"; then
echo "::notice title=solinv::$src is new since the base (or newly a program); nothing to compare"
continue
fi
# A program the change deleted takes every check it had: a failure,
# once (the next change's base no longer has it). A native one's
# findings are warnings, so this is too.
if [ ! -e "$src" ]; then
if [ "$FAIL" = "true" ] && anchor "$RUNNER_TEMP/solinv-base/$src"; then
echo "::error title=solinv::$src is gone from the head, with every account check it had"
status=1
else
echo "::warning title=solinv::$src is gone from the head, with every account check it had"
fi
continue
fi
echo "::group::solinv diff $src"
"$RUNNER_TEMP/solinv-bin/bin/solinv" diff \
--base-src "$RUNNER_TEMP/solinv-base/$src" \
--head-src "$src" \
--format github $flag || status=1
echo "::endgroup::"
done <<< "$SRC"
exit $status