Skip to content

Commit eb41145

Browse files
committed
first cut at API checking
It turns out there is a consumer of cabal-install-solver, so I have added it to API generation and checking.
1 parent 3727226 commit eb41145

File tree

9 files changed

+23248
-1
lines changed

9 files changed

+23248
-1
lines changed

.github/workflows/check-api.skip.yml

+49
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
2+
name: Check API Skip
3+
4+
# This Workflow is special and contains a workaround for a known limitation of GitHub CI.
5+
#
6+
# The problem: We don't want to run the "check-api" jobs on PRs which contain only changes
7+
# to the docs, since these jobs take a long time to complete without providing any benefit.
8+
# We therefore use path-filtering in the workflow triggers for the bootstrap jobs, namely
9+
# "paths-ignore: doc/**". But the "Check API post job" is a required job, therefore a PR cannot
10+
# be merged unless the "Check API post job" completes succesfully, which it doesn't do if we
11+
# filter it out.
12+
#
13+
# The solution: We use a second job with the same name which always returns the exit code 0.
14+
# The logic implemented for "required" workflows accepts if 1) at least one job with that name
15+
# runs through, AND 2) If multiple jobs of that name exist, then all jobs of that name have to
16+
# finish successfully.
17+
on:
18+
push:
19+
paths:
20+
- 'doc/**'
21+
- '**/README.md'
22+
- 'CONTRIBUTING.md'
23+
- "changelog.d/**"
24+
# only top level for these, because various test packages have them too
25+
- "*/ChangeLog.md"
26+
- "*/changelog.md"
27+
- "release-notes/**"
28+
branches:
29+
- master
30+
pull_request:
31+
paths:
32+
- 'doc/**'
33+
- '**/README.md'
34+
- 'CONTRIBUTING.md'
35+
- "changelog.d/**"
36+
- "*/ChangeLog.md"
37+
- "*/changelog.md"
38+
- "release-notes/**"
39+
release:
40+
types:
41+
- created
42+
43+
jobs:
44+
check-api-post-job:
45+
if: always()
46+
name: Check API post job
47+
runs-on: ubuntu-latest
48+
steps:
49+
- run: exit 0

.github/workflows/check-api.yml

+153
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
name: Check API
2+
3+
on:
4+
push:
5+
paths-ignore:
6+
- "doc/**"
7+
- "**/README.md"
8+
- "CONTRIBUTING.md"
9+
- "changelog.d/**"
10+
# only top level for these, because various test packages have them too
11+
- "*/ChangeLog.md"
12+
- "*/changelog.md"
13+
- "release-notes/**"
14+
branches:
15+
- master
16+
pull_request:
17+
paths-ignore:
18+
- "doc/**"
19+
- "**/README.md"
20+
- "CONTRIBUTING.md"
21+
- "changelog.d/**"
22+
- "*/ChangeLog.md"
23+
- "*/changelog.md"
24+
- "release-notes/**"
25+
release:
26+
types:
27+
- created
28+
workflow_call:
29+
30+
jobs:
31+
check-api:
32+
name: Check API using ${{ matrix.sys.os }} ghc-${{ matrix.ghc }}
33+
runs-on: ${{ matrix.sys.os }}
34+
strategy:
35+
matrix:
36+
# we check API only on one platform and ghc release, since it shouldn't
37+
# vary elsewhere (hopefully) and the API tracer is sensitive to both
38+
sys:
39+
- { os: ubuntu-latest }
40+
ghc:
41+
[
42+
# print-api only supports a small subset of ghc versions
43+
"9.10.1",
44+
]
45+
46+
steps:
47+
48+
- uses: actions/checkout@v4
49+
50+
- uses: haskell-actions/setup@v2
51+
id: setup-haskell
52+
with:
53+
ghc-version: ${{ matrix.ghc }}
54+
cabal-version: 3.12.1.0 # see https://github.com/haskell/cabal/pull/10251
55+
ghcup-release-channel: https://raw.githubusercontent.com/haskell/ghcup-metadata/master/ghcup-prereleases-0.0.8.yaml
56+
57+
# I was going to use the canned action, but it only supports a single package and reinstalls the same binary each time
58+
- name: Install print-api
59+
run: |
60+
wget -q https://github.com/Kleidukos/print-api/releases/download/v0.1.0.1/print-api-0.1.0.1-Linux-static-${{ matrix.ghc }}-x86_64.tar.gz
61+
tar -xzf print-api-0.1.0.1-Linux-static-${{ matrix.ghc }}-x86_64.tar.gz
62+
mkdir -p "$HOME/.local/bin"
63+
mv print-api "$HOME/.local/bin/print-api"
64+
chmod +x "$HOME/.local/bin/print-api"
65+
echo "$HOME/.local/bin" >> $GITHUB_PATH
66+
67+
# print-api needs environment files. It also doesn't make a lot of sense to use the cached builds, sadly,
68+
# since they're special in different ways (bootstrap and validate) and we want a vanilla build. And there
69+
# isn't enough cache space to make a third cache, even though this is a very limited build.
70+
- name: Build Cabal with environment files
71+
run: |
72+
cabal build Cabal-syntax Cabal cabal-install-solver --write-ghc-environment-files=always
73+
if test -d Cabal-hooks; then
74+
cabal build Cabal-hooks --write-ghc-environment-files=always
75+
fi
76+
77+
- name: Generate APIs
78+
run: make generate-api
79+
80+
# upload the new API records as artifacts, since there's no guarantee that a contributor could produce
81+
# them (wrong platform or ghc version). This must happen _before_ we check the API, because the
82+
# point is to have them available on API mismatch so they can be updated.
83+
- uses: actions/upload-artifact@v4
84+
with:
85+
name: Cabal-api
86+
path: '*.api'
87+
88+
- name: Check APIs
89+
run: |
90+
rc=0
91+
if diff -c Cabal-syntax/Cabal-syntax-${{ matrix.ghc }}.api Cabal-syntax-${{ matrix.ghc }}.api >api.tmp; then
92+
:
93+
else
94+
echo "Cabal-syntax API changed"
95+
if [ $(wc -l < api.tmp) -lt 50 ]; then
96+
cat api.tmp
97+
else
98+
echo Diff too large for GitHub viewer
99+
fi
100+
rc=1
101+
fi
102+
if diff -c Cabal/Cabal-${{ matrix.ghc }}.api Cabal-${{ matrix.ghc }}.api >api.tmp; then
103+
:
104+
else
105+
echo "Cabal API changed"
106+
if [ $(wc -l < api.tmp) -lt 50 ]; then
107+
cat api.tmp
108+
else
109+
echo Diff too large for GitHub viewer
110+
fi
111+
rc=1
112+
fi
113+
if test \! -d Cabal-hooks; then
114+
echo "No Cabal-hooks in this version"
115+
elif diff -c Cabal-hooks/Cabal-hooks-${{ matrix.ghc }}.api Cabal-hooks-${{ matrix.ghc }}.api >api.tmp; then
116+
:
117+
else
118+
echo "Cabal-hooks API changed"
119+
if [ $(wc -l < api.tmp) -lt 50 ]; then
120+
cat api.tmp
121+
else
122+
echo Diff too large for GitHub viewer
123+
fi
124+
rc=1
125+
fi
126+
if diff -c cabal-install-solver/cabal-install-solver-${{ matrix.ghc }}.api cabal-install-solver-${{ matrix.ghc }}.api >api.tmp; then
127+
:
128+
else
129+
echo "cabal-install-solver API changed"
130+
if [ $(wc -l < api.tmp) -lt 50 ]; then
131+
cat api.tmp
132+
else
133+
echo Diff too large for GitHub viewer
134+
fi
135+
rc=1
136+
fi
137+
if [ $rc -ne 0 ]; then
138+
echo "The new APIs are in the artifact uploaded in the previous step."
139+
exit $rc
140+
fi
141+
142+
# See check-api.skip.yml for why we need this
143+
check-api-post-job:
144+
if: always()
145+
name: Check API post job
146+
runs-on: ubuntu-latest
147+
needs: check-api
148+
149+
steps:
150+
- run: |
151+
echo "jobs info: ${{ toJSON(needs) }}"
152+
- if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')
153+
run: exit 1

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -108,3 +108,6 @@ bench.html
108108

109109
# ignore the downloaded binary files
110110
scripts/release/binary-downloads/
111+
112+
# ignore generated API files
113+
/*.api

CONTRIBUTING.md

+39
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,46 @@ you push a fix of a whitespace violation, please do so in a _separate commit_. F
247247
`make whitespace` will show violations and `make fix-whitespace` will fix them, if the
248248
`fix-whitespace` utility is installed.
249249
250+
## API Changes and Check API job
251+
-----------------------------
252+
253+
The `Check API` job tests the `Cabal`, `Cabal-syntax`, and `cabal-install-solver`
254+
packages for API changes. It's useful to indicate when a changelog is needed and
255+
which PRs aren't appropriate for backports.
256+
257+
If the `Check API` job fails, you will find in its build artifacts (at the bottom
258+
of the "upload artifacts" step, immediately before the actual API check) a ZIP file
259+
containing the new API records. You can download this and replace the existing API
260+
descriptions, which can be found in the package top level directories, with `.api`
261+
suffixes. Generating them locally is possible with the [check-api tool](https://github.com/Kleidukos/print-api), but
262+
is not guaranteed to produce the same result as the CI job does.
263+
264+
If you do wish to generate a local API record, install [`print-api`](https://github.com/Kleidukos/print-api/releases/tag/v0.1.0.1) and
265+
run it on the `Cabal`, `Cabal-syntax`, and `cabal-install-solver` packages, from
266+
the top level directory of the Cabal repo:
267+
268+
make generate-api
269+
270+
You will need `ghc-9.10.1` to be on `$PATH`; `ghcup` is the easiest way to do this.
271+
272+
The resulting `Cabal-syntax.api`, `Cabal.api`, and `cabal-install-solver` files
273+
can then be compared to the ones in the `Cabal-syntax`, `Cabal`, and
274+
`cabal-install-solver` package directories.
275+
276+
make check-api
277+
278+
If necessary, you can then install the API records:
279+
280+
make update-api
281+
282+
It is also possible to do this individually; see the `Makefile`.
283+
284+
Note that different compiler versions and different architectures will alter the
285+
output. It is not expected that different Linux distributions will, but you may
286+
need to use the static build if you aren't using Ubuntu 22.04.
287+
250288
## Other Conventions
289+
-----------------
251290
252291
* Format your commit messages [in the standard way](https://chris.beams.io/posts/git-commit/#seven-rules).
253292

0 commit comments

Comments
 (0)