-
Notifications
You must be signed in to change notification settings - Fork 0
265 lines (239 loc) · 12.1 KB
/
Copy pathrelease.yml
File metadata and controls
265 lines (239 loc) · 12.1 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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
name: Release
# Triggers on tag push (e.g. `git push origin v0.2.0`). Builds the Conan
# package on every consumer platform (Linux, macOS, Windows), uploads each
# binary to the project's Cloudsmith remote, and publishes a GitHub Release
# with auto-generated notes.
#
# Why a matrix: Cloudsmith stores one binary package per platform/compiler
# (package_id). A single ubuntu job only ever publishes a Linux/gcc binary, so
# macOS and Windows consumers are forced to build plotjuggler_sdk from source
# via `--build=missing`. That is slow and, on macOS specifically, fragile: a
# from-source build depends on the recipe's exported sources being intact in
# the cache, which breaks (e.g. after a version is re-published with a new
# recipe revision) with "exports_sources but sources not found in local cache".
# Publishing a binary per platform means consumers download instead of compile.
#
# Secrets required (set in repo Settings -> Secrets and variables -> Actions):
# CLOUDSMITH_USER - Cloudsmith username (e.g. "davide-faconti")
# CLOUDSMITH_API_KEY - Cloudsmith API key with write access to the
# plotjuggler/plotjuggler repository
#
# Manual trigger: workflow_dispatch lets you re-run for an existing tag if the
# first attempt failed (e.g. flaky upload on one platform). Set `tag` input to
# e.g. v0.1.0. NOTE: do not *move* an already-released tag to a new commit —
# cut a new patch version instead. Moving a tag changes the recipe revision on
# the remote and orphans any consumer mid-resolve. The `prepare` job enforces
# this: it refuses to publish a version that already exists on Cloudsmith under
# a different recipe revision (override with the `allow_republish` input only to
# repair a botched release).
on:
push:
tags:
- 'v*'
workflow_dispatch:
inputs:
tag:
description: 'Tag to (re-)release (e.g. v0.1.0). Must already exist.'
required: true
allow_republish:
description: 'Bypass the re-publish guard (only to repair a botched release of an existing version)'
type: boolean
default: false
required: false
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false # never cancel an in-flight release
jobs:
# ---------------------------------------------------------------------------
# Resolve the version/tag once and verify it matches conanfile.py, so the
# build matrix and the GitHub Release all agree on a single source of truth.
# ---------------------------------------------------------------------------
prepare:
runs-on: ubuntu-22.04
permissions:
contents: read
outputs:
ref: ${{ steps.ref.outputs.ref }}
tag: ${{ steps.ref.outputs.tag }}
version: ${{ steps.ref.outputs.version }}
steps:
- name: Resolve ref
id: ref
run: |
if [[ -n "${{ inputs.tag }}" ]]; then
echo "ref=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
else
echo "ref=${GITHUB_REF}" >> "$GITHUB_OUTPUT"
echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
fi
# Strip leading 'v' for the Conan version (v0.1.0 -> 0.1.0).
version="${GITHUB_REF_NAME#v}"
version="${version:-${{ inputs.tag }}}"
version="${version#v}"
echo "version=${version}" >> "$GITHUB_OUTPUT"
- uses: actions/checkout@v4
with:
ref: ${{ steps.ref.outputs.ref }}
- uses: conan-io/setup-conan@v1
- name: Verify recipe version matches tag
# Prevents the common footgun of tagging vX.Y.Z but forgetting to bump
# `version` in conanfile.py. Fails fast before we publish.
run: |
recipe_version=$(python3 -c "import re; print(re.search(r'^\s*version\s*=\s*\"([^\"]+)\"', open('conanfile.py').read(), re.M).group(1))")
if [[ "${recipe_version}" != "${{ steps.ref.outputs.version }}" ]]; then
echo "::error::conanfile.py version (${recipe_version}) does not match tag (${{ steps.ref.outputs.version }})"
exit 1
fi
echo "Version match: ${recipe_version}"
- name: Guard against re-publishing an existing version with different sources
# The incident this matrix fixes was triggered by *moving* a released tag:
# v0.5.0 was published twice from two different commits, so the second run
# produced a new recipe revision that replaced the first and orphaned any
# consumer that had resolved the original mid-build. This guard computes
# the recipe revision THIS release would publish and compares it to what is
# already on Cloudsmith for this version:
# * version not published yet -> proceed (first release)
# * same recipe revision present -> proceed (idempotent re-run after a
# flaky/partial upload)
# * a different revision present -> FAIL (sources changed under an
# already-released version)
# prepare runs on Linux (LF); the build matrix forces an LF checkout, so
# both compute the same canonical recipe revision.
env:
CLOUDSMITH_USER: ${{ secrets.CLOUDSMITH_USER }}
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
run: |
if [[ "${{ inputs.allow_republish }}" == "true" ]]; then
echo "allow_republish=true -> skipping the re-publish guard"
exit 0
fi
version="${{ steps.ref.outputs.version }}"
# Recipe revision this release would publish (deterministic from the
# checked-out sources, independent of build settings).
conan profile detect --force >/dev/null 2>&1 || true
local_rrev=$(conan export . --format=json \
| python3 -c "import json,sys; print(json.load(sys.stdin)['reference'].split('#',1)[1])")
echo "This release would publish recipe revision: ${local_rrev}"
conan remote add plotjuggler-cloudsmith https://conan.cloudsmith.io/plotjuggler/plotjuggler --force
if [[ -n "$CLOUDSMITH_USER" && -n "$CLOUDSMITH_API_KEY" ]]; then
conan remote login plotjuggler-cloudsmith "$CLOUDSMITH_USER" -p "$CLOUDSMITH_API_KEY"
fi
conan list "plotjuggler_sdk/${version}#*" -r plotjuggler-cloudsmith --format=json \
> /tmp/remote_revs.json 2>/dev/null || true
if grep -q "RECIPEUNKNOWN" /tmp/remote_revs.json; then
echo "Version ${version} is not yet published — first release, proceeding."
elif grep -q '"error"' /tmp/remote_revs.json; then
echo "::warning::Unexpected error reading published revisions for ${version} (transient?). Proceeding without the guard."
cat /tmp/remote_revs.json
elif grep -q "${local_rrev}" /tmp/remote_revs.json; then
echo "Recipe revision ${local_rrev} is already published for ${version} — idempotent re-run, proceeding."
else
echo "::error::plotjuggler_sdk/${version} is already published with a different recipe revision."
echo "::error::This build would publish ${local_rrev}, which is not on the remote. Refusing to overwrite a released version."
echo "::error::Cut a new version, or re-run via workflow_dispatch with allow_republish=true to repair a botched release."
echo "Currently published for ${version}:"
conan list "plotjuggler_sdk/${version}#*" -r plotjuggler-cloudsmith 2>/dev/null || true
exit 1
fi
# ---------------------------------------------------------------------------
# Build + upload one Conan binary per consumer platform. fail-fast: false so
# a transient failure on one OS still publishes the others (re-run the
# workflow_dispatch for the failed leg); the GitHub Release is gated on ALL
# legs succeeding so we never advertise an incomplete release.
# ---------------------------------------------------------------------------
build:
needs: prepare
permissions:
contents: read
strategy:
fail-fast: false
matrix:
os: [ubuntu-22.04, macos-15-intel, windows-latest]
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash # Git Bash on Windows; conan/cmake are on PATH everywhere
steps:
- name: Force LF line endings (consistent recipe revision across OSes)
# No .gitattributes in the repo, so on Windows git would convert text
# files to CRLF on checkout, changing the recipe revision hash and
# splitting the package across two revisions. Force LF everywhere so every
# matrix leg computes and uploads the SAME recipe revision (matching the
# one prepare's guard validated on Linux).
run: |
git config --global core.autocrlf false
git config --global core.eol lf
- uses: actions/checkout@v4
with:
ref: ${{ needs.prepare.outputs.ref }}
- uses: conan-io/setup-conan@v1
- name: Detect Conan profile
run: |
conan profile detect --force
conan profile show
- name: Build Conan package
# Same settings consumers use (-s build_type=Release -s
# compiler.cppstd=20) so the published package_id matches what
# downstream `conan install` resolves on each platform.
run: |
conan create . \
--build=missing \
-s build_type=Release \
-s compiler.cppstd=20
- name: Configure Cloudsmith remote
env:
CLOUDSMITH_USER: ${{ secrets.CLOUDSMITH_USER }}
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
run: |
if [[ -z "$CLOUDSMITH_USER" || -z "$CLOUDSMITH_API_KEY" ]]; then
echo "::error::CLOUDSMITH_USER / CLOUDSMITH_API_KEY secrets not configured"
exit 1
fi
# `add ... --force` makes the step idempotent across re-runs.
conan remote add plotjuggler-cloudsmith https://conan.cloudsmith.io/plotjuggler/plotjuggler --force
conan remote login plotjuggler-cloudsmith "$CLOUDSMITH_USER" -p "$CLOUDSMITH_API_KEY"
- name: Upload package to Cloudsmith
# Each matrix leg uploads the recipe + its own binary. The recipe
# revision is identical across platforms (same recipe content), so
# concurrent recipe uploads are idempotent — only the per-platform
# binary differs. `--check` verifies integrity before/after transfer.
run: |
conan upload "plotjuggler_sdk/${{ needs.prepare.outputs.version }}" \
-r plotjuggler-cloudsmith \
--confirm \
--check
# ---------------------------------------------------------------------------
# Cut the GitHub Release once, only after every platform binary is published.
# ---------------------------------------------------------------------------
github-release:
needs: [prepare, build]
runs-on: ubuntu-22.04
permissions:
contents: write # required to create the GitHub Release
steps:
- name: Create GitHub Release
# softprops/action-gh-release: handles auto-generated notes + idempotent
# re-runs (skips if a release for the tag already exists).
uses: softprops/action-gh-release@v2
with:
tag_name: ${{ needs.prepare.outputs.tag }}
name: plotjuggler_sdk ${{ needs.prepare.outputs.tag }}
generate_release_notes: true
body: |
## Install via Conan
```bash
conan remote add plotjuggler https://conan.cloudsmith.io/plotjuggler/plotjuggler
```
Add to your `conanfile.py` / `conanfile.txt`:
```python
requires = ("plotjuggler_sdk/${{ needs.prepare.outputs.version }}",)
```
Link in CMake:
```cmake
find_package(plotjuggler_sdk REQUIRED COMPONENTS plugin_sdk)
target_link_libraries(my_plugin PRIVATE plotjuggler_sdk::plugin_sdk)
```
See [README.md](https://github.com/PlotJuggler/plotjuggler_sdk/blob/main/README.md)
for available components (`base`, `plugin_sdk`, `plugin_host`)
and consumer examples.