-
Notifications
You must be signed in to change notification settings - Fork 0
216 lines (204 loc) · 10.3 KB
/
Copy pathci.yml
File metadata and controls
216 lines (204 loc) · 10.3 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
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
name: CI
# Docs carry no build risk, and this workflow's linux leg is a ~2h serial
# gRPC build (see timeout-minutes below) — a markdown-only change would spend
# it twice, once on the PR and once again on main. `paths-ignore` on both
# triggers, not just the PR one, or the merge pays the second half anyway.
#
# Deliberately NOT ignored: mcpp.toml, build.mcpp, templates/, examples/,
# rules/, plugin/, src/ — the contract checks (templates/greeter ==
# examples/greeter, source list == upstream CMakeLists) read those.
on:
push:
branches: [main]
paths-ignore: ["**.md", ".agents/**", "LICENSE"]
pull_request:
paths-ignore: ["**.md", ".agents/**", "LICENSE"]
env:
# Keep in step with the mcpp-index CI pin: this package's dependencies are
# index packages, and they are validated against that same mcpp.
# 2026.8.6.2 is the FLOOR, not a routine pin. Four things the codegen in
# templates/ and examples/ is built on do not exist before it:
# 2026.8.5.1 `tools = [...]` host tools; `mcpp:action=` build-graph nodes
# 2026.8.5.2 a `host-module = true` rule package may use `import std;` and
# `import mcpp;` — rules/ needs both, and before .5.2 it failed
# with `module 'std' not found` / `module 'mcpp' not found`
# 2026.8.6.2 `reexport = true`, which is what lets THIS package hand its
# user the whole codegen toolchain — one dependency, not four —
# and `rerun_if_changed_glob`, without which generate_all()
# would not re-run when a .proto is ADDED (no declared file's
# hash changes), silently never generating it
MCPP_VERSION: "2026.8.6.2"
jobs:
build:
name: ${{ matrix.name }}
runs-on: ${{ matrix.os }}
# Every example is its own project depending on `grpc` by path, and each
# one builds gRPC from source again — the cache does not carry across
# them. Measured on linux: module test 25min, then ~31min per example.
# Three examples already sat at ~90min under the old 120 cap; adding
# `advanced` pushed the run past it and the job was cancelled mid-build,
# which reads like a failure and is not one.
#
# Raising the cap is the honest short-term answer, not the right one:
# what actually costs the hour and a half is rebuilding the same gRPC
# four times in series. Splitting the examples into their own matrix leg
# (or getting the dependency cache to hit across sibling projects) turns
# ~2h of wall clock into ~35min. Worth doing on its own, not inside a
# release PR.
timeout-minutes: 210
strategy:
fail-fast: false
matrix:
include:
- { name: "linux x86_64", os: ubuntu-latest, suffix: linux-x86_64, ext: tar.gz, mcpp: bin/mcpp }
- { name: "macos arm64", os: macos-latest, suffix: macosx-arm64, ext: tar.gz, mcpp: bin/mcpp }
# No windows leg: compat.openssl has no windows xpm entry yet, so
# dependency resolution fails there before anything is compiled
# ("E_NOT_FOUND: package 'compat:openssl@3.5.1' not found"). gRPC's
# secure build cannot drop TLS, so this package is linux + macOS
# until that package gains windows support. Re-add this line then —
# the windows flags in mcpp.toml are already in place.
steps:
- uses: actions/checkout@v4
- name: Download pinned mcpp
shell: bash
run: |
base="https://github.com/mcpp-community/mcpp/releases/download/v${MCPP_VERSION}"
archive="mcpp-${MCPP_VERSION}-${{ matrix.suffix }}.${{ matrix.ext }}"
curl -L -fsS -o "$archive" "$base/$archive"
if [ "${{ matrix.ext }}" = "zip" ]; then unzip -q "$archive"; else tar -xzf "$archive"; fi
root="$PWD/mcpp-${MCPP_VERSION}-${{ matrix.suffix }}"
if [ "${{ runner.os }}" = "Windows" ]; then
echo "MCPP=$(cygpath -m "$root/${{ matrix.mcpp }}")" >> "$GITHUB_ENV"
echo "MCPP_VENDORED_XLINGS=$(cygpath -m "$root/registry/bin/xlings")" >> "$GITHUB_ENV"
else
echo "MCPP=$root/${{ matrix.mcpp }}" >> "$GITHUB_ENV"
echo "MCPP_VENDORED_XLINGS=$root/registry/bin/xlings" >> "$GITHUB_ENV"
fi
# The dependencies are published index packages, and the snapshot the
# mcpp release vendored is older than whatever landed since.
- name: Refresh the published package index
shell: bash
env:
MCPP_INDEX_MIRROR: GLOBAL
run: |
"$MCPP" index update
# mcpp#344: object-path disambiguation keys on basename collisions across
# the whole build dir, while the package cache key covers only the
# dependency — one entry can hold two layouts. This graph has several
# colliding basenames (abseil/protobuf `parser.cc`, abseil/gRPC
# `status.cc`, `time.cc`), so the package cache is bypassed exactly as
# mcpp-index's own CI does. Drop this once the pin reaches 2026.8.3.4.
- name: Build the library and run the module test
shell: bash
env:
MCPP_INDEX_MIRROR: GLOBAL
MCPP_BUILD_CACHE: local
run: |
"$MCPP" --version
"$MCPP" test
# The long form: codegen spelled out by hand in build.mcpp.
- name: helloworld example — a real RPC over loopback
shell: bash
working-directory: examples/helloworld
env:
MCPP_INDEX_MIRROR: GLOBAL
MCPP_BUILD_CACHE: local
run: |
"$MCPP" run
# The short form: the same program, with codegen from the `grpcgen` rule
# package and a three-line build.mcpp. This example IS templates/greeter
# instantiated, so building it is how the template gets tested — a broken
# template would otherwise only be discovered by a user running
# `mcpp new`.
- name: greeter example — the template, via the rule package
shell: bash
working-directory: examples/greeter
env:
MCPP_INDEX_MIRROR: GLOBAL
MCPP_BUILD_CACHE: local
run: |
"$MCPP" run
# The layered knobs, in the shape a real project hits first: a shared
# .proto tree that must be GENERATED (not merely searched), gRPC's mock
# generation, and plan/submit with an assertion in between. greeter
# covers L0; without this one, everything past the two original options
# is untested.
- name: advanced example — extra_dirs + mock + plan/submit
shell: bash
working-directory: examples/advanced
env:
MCPP_INDEX_MIRROR: GLOBAL
MCPP_BUILD_CACHE: local
run: |
"$MCPP" run
# The mock header is declared by the edge and produced by protoc;
# nothing includes it (no gmock in this ecosystem), so assert the
# file itself rather than trusting the plan-time check alone.
out=$(find target/.build-mcpp/out -name 'orders_mock.grpc.pb.h' | head -1)
test -n "$out" || { echo "no mock header produced"; exit 1; }
echo "mock header: $out"
# The shared tree was generated, not just searched.
test -f target/.build-mcpp/out/common/types.pb.cc \
|| { echo "shared-proto was not generated"; exit 1; }
# The edge describes ITSELF. This is the rule's half of observability
# — mcpp already puts the full argv in build.ninja, so what the rule
# owns is "which knobs produced it", and that string is what every
# build prints. Asserting it keeps the two from drifting apart.
nj=$(find target -name build.ninja | head -1)
grep -q 'description = GENERATE protoc:orders (+grpc +mock,' "$nj" \
|| { echo "edge description does not describe its knobs:";
grep -o 'description = GENERATE protoc:.*' "$nj"; exit 1; }
grep -o 'description = GENERATE protoc:.*' "$nj"
package-versions-match:
name: all three packages share one version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# The plugin generates code that calls gRPC internals, so a plugin built
# from a different release than the runtime being linked is exactly the
# mismatch this whole design exists to make impossible. The rule package
# names both of the others by version in what it tells users to write.
# All three ship as separate index entries from ONE tag, so nothing but a
# check keeps them equal.
- name: versions must match
shell: bash
run: |
lib=$(awk -F'"' '/^version/{print $2; exit}' mcpp.toml)
plug=$(awk -F'"' '/^version/{print $2; exit}' plugin/mcpp.toml)
rule=$(awk -F'"' '/^version/{print $2; exit}' rules/mcpp.toml)
echo "grpc=$lib grpc-plugin=$plug grpcgen=$rule"
rc=0
[ "$lib" = "$plug" ] || { echo "FAIL: grpc-plugin ($plug) != grpc ($lib)"; rc=1; }
[ "$lib" = "$rule" ] || { echo "FAIL: grpcgen ($rule) != grpc ($lib)"; rc=1; }
exit $rc
template-matches-example:
name: templates/greeter == examples/greeter
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# examples/greeter is the template instantiated; CI builds it, which is
# the only thing that tests the template at all. That only holds while
# the two do not drift, and build.mcpp is the file that matters — it is
# pure code with no substitutions, so it must be byte-identical.
- name: build.mcpp must be identical
shell: bash
run: |
if ! diff -u templates/greeter/build.mcpp.in examples/greeter/build.mcpp; then
echo "FAIL: the template and its example have drifted apart."
echo " cp templates/greeter/build.mcpp.in examples/greeter/build.mcpp"
exit 1
fi
echo "template and example agree"
manifest-matches-upstream:
name: source list == upstream CMakeLists
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Fetch the pinned gRPC tag
run: |
git clone -q --depth 1 -b v1.83.0 https://github.com/grpc/grpc.git /tmp/grpc
# Proves mcpp.toml's source arrays are upstream's own list, not a
# hand-curated one that has drifted.
- name: tools/gen_sources.py --check
run: python3 tools/gen_sources.py --grpc /tmp/grpc --check