Skip to content

Commit 2e958e1

Browse files
facontidavideDavide Faconticlaude
authored
feat: package as Conan recipe with plotjuggler_core:: CMake components (#89)
* feat: package as Conan recipe with plotjuggler_core:: CMake components Renames the installable CMake package from PlotJugglerSDK to plotjuggler_core, restructures install targets into four audience-aligned components, and adds a Conan 2 recipe so downstream projects (e.g. pj-official-plugins) can resolve plotjuggler_core via `find_package` + `requires` instead of CPM source pulls. Components: base - vocabulary types (pj_base, always available) datastore - columnar engine (option: with_datastore) plugin_sdk - umbrella for plugin authors (pj_base + pj_dialog_sdk + parser SDK); ships PjPluginManifest.cmake via cmake_build_modules plugin_host - umbrella for host loaders (option: with_host) Usage: find_package(plotjuggler_core REQUIRED COMPONENTS plugin_sdk) target_link_libraries(my_plugin PRIVATE plotjuggler_core::plugin_sdk) Verified: ./build.sh + 48/48 tests, ./test_sdk_install.sh (all 4 components resolve via find_package, sdk_consumer example emits manifest sidecar), `conan create .` packages cleanly, and an out-of-tree Conan consumer can link plotjuggler_core::{base,datastore,plugin_sdk,plugin_host}. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: set initial release version 0.1.0 Bumps the placeholder 1.0.0 version (carried over from PJ_SDK_VERSION) to 0.1.0 as the first published release of the plotjuggler_core Conan package and installable CMake package. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: add release workflow — tag push uploads to Cloudsmith + creates GitHub Release Triggered by pushing a v* tag (or manually via workflow_dispatch). Verifies the conanfile.py version matches the tag, builds the package with `conan create`, uploads it to the project's Cloudsmith remote, and publishes a GitHub Release with install instructions. Requires repo secrets: CLOUDSMITH_USER — Cloudsmith username CLOUDSMITH_API_KEY — Cloudsmith API key with write access Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * ci: switch cache-key hash to conanfile.py (top-level conanfile.txt removed) The Windows CI step `Compute Conan cache tag` was hashing conanfile.txt by name, which broke after the file was replaced by conanfile.py. Switch the Windows hash input to conanfile.py and broaden the Linux find to match either filename so subtree conanfile.txt files (e.g. pj_ported_plugins/) continue to influence the cache key. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: export plugin loader detail in package * ci: install test dependencies on Windows * ci: fall back to main Conan cache * ci: speed up Linux PR builds --------- Co-authored-by: Davide Faconti <dfaconti@aurynrobotics.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 3ef471e commit 2e958e1

17 files changed

Lines changed: 700 additions & 153 deletions

.github/workflows/linux-ci.yml

Lines changed: 62 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -72,17 +72,22 @@ jobs:
7272
shell: bash
7373
run: |
7474
set -euo pipefail
75-
# Hash every conanfile.txt under the tree (top-level today,
76-
# pj_ported_plugins/ in the near future) so the key invalidates
77-
# whenever any of them changes.
78-
hash=$(find . -name conanfile.txt -not -path './build/*' -not -path './.git/*' \
75+
# Hash every conanfile.{py,txt} under the tree so the key invalidates
76+
# whenever any of them changes. The top-level is now conanfile.py
77+
# (Conan recipe); subtrees like pj_ported_plugins/ still use .txt.
78+
hash=$(find . \( -name 'conanfile.py' -o -name 'conanfile.txt' \) \
79+
-not -path './build/*' -not -path './.git/*' \
7980
-print0 | sort -z | xargs -0 sha256sum | sha256sum | cut -c1-16)
8081
owner="${GITHUB_REPOSITORY_OWNER,,}"
8182
# Build-type suffix leaves room for a future debug-asan variant
8283
# without colliding on the same tag.
8384
tag="ghcr.io/${owner}/plotjuggler-core-conan-cache:linux-gcc-relwithdebinfo-${hash}"
85+
main_tag="ghcr.io/${owner}/plotjuggler-core-conan-cache:linux-gcc-relwithdebinfo-main"
86+
echo "hash=${hash}" >> "${GITHUB_OUTPUT}"
8487
echo "tag=${tag}" >> "${GITHUB_OUTPUT}"
88+
echo "main_tag=${main_tag}" >> "${GITHUB_OUTPUT}"
8589
echo "Cache tag: ${tag}"
90+
echo "Main fallback cache tag: ${main_tag}"
8691
8792
- name: Login to ghcr.io
8893
# Required for both pull (private packages) and push. GITHUB_TOKEN is
@@ -100,23 +105,33 @@ jobs:
100105
shell: bash
101106
run: |
102107
set +e
103-
tag="${{ steps.conan_cache.outputs.tag }}"
108+
exact_tag="${{ steps.conan_cache.outputs.tag }}"
109+
main_tag="${{ steps.conan_cache.outputs.main_tag }}"
104110
pull_dir="${RUNNER_TEMP}/conan-cache-pull"
105111
mkdir -p "${pull_dir}"
106-
(
107-
cd "${pull_dir}"
108-
oras pull "${tag}" 2>&1
109-
)
110-
pulled=$?
111112
tarball="${pull_dir}/conan-cache.tar.zst"
112-
if [ "${pulled}" -eq 0 ] && [ -f "${tarball}" ]; then
113-
echo "Cache HIT: extracting ${tarball}"
114-
mkdir -p "${HOME}/.conan2"
115-
tar --zstd -xf "${tarball}" -C "${HOME}"
116-
echo "CONAN_CACHE_HIT=1" >> "${GITHUB_ENV}"
117-
else
118-
echo "Cache MISS for ${tag} (cold start or first build of this conanfile)"
119-
fi
113+
114+
for tag in "${exact_tag}" "${main_tag}"; do
115+
rm -f "${tarball}"
116+
echo "Trying Conan cache: ${tag}"
117+
(
118+
cd "${pull_dir}"
119+
oras pull "${tag}" 2>&1
120+
)
121+
pulled=$?
122+
if [ "${pulled}" -eq 0 ] && [ -f "${tarball}" ]; then
123+
echo "Cache HIT: extracting ${tarball}"
124+
mkdir -p "${HOME}/.conan2"
125+
tar --zstd -xf "${tarball}" -C "${HOME}"
126+
echo "CONAN_CACHE_HIT=1" >> "${GITHUB_ENV}"
127+
echo "CONAN_CACHE_HIT_TAG=${tag}" >> "${GITHUB_ENV}"
128+
if [ "${tag}" = "${exact_tag}" ]; then
129+
echo "CONAN_CACHE_EXACT_HIT=1" >> "${GITHUB_ENV}"
130+
fi
131+
exit 0
132+
fi
133+
echo "Cache MISS for ${tag}"
134+
done
120135
# Always exit 0 — a missing tag is not a CI failure.
121136
exit 0
122137
@@ -128,17 +143,18 @@ jobs:
128143
cache: 'true'
129144

130145
- name: Install system packages
131-
run: sudo apt-get update && sudo apt-get install -y xvfb ccache zstd
146+
run: sudo apt-get update && sudo apt-get install -y xvfb ccache ninja-build zstd
132147

133148
- name: Configure ccache
134149
# Compiler-output cache for our own C++ — complementary to the Conan
135150
# cache (which holds prebuilt third-party packages, not our objects).
136151
uses: actions/cache@v4
137152
with:
138153
path: ~/.cache/ccache
139-
key: ccache-linux-${{ github.ref_name }}-${{ github.sha }}
154+
key: ccache-linux-relwithdebinfo-${{ steps.conan_cache.outputs.hash }}-${{ github.sha }}
140155
restore-keys: |
141-
ccache-linux-${{ github.ref_name }}-
156+
ccache-linux-relwithdebinfo-${{ steps.conan_cache.outputs.hash }}-
157+
ccache-linux-relwithdebinfo-
142158
ccache-linux-
143159
144160
- name: Set ccache limits
@@ -147,15 +163,19 @@ jobs:
147163
ccache --set-config=compression=true
148164
ccache -z
149165
150-
- name: Build
151-
run: ./build.sh
166+
- name: Conan install
167+
run: >
168+
conan install . --output-folder=build --build=missing
169+
-s build_type=RelWithDebInfo -s compiler.cppstd=20
170+
-o "plotjuggler_core/*:with_tests=True"
171+
-o "plotjuggler_core/*:with_parquet_example=False"
152172
153173
- name: Save Conan cache to ghcr.io
154174
# Only push from the canonical repo on real pushes (forks lack write
155-
# access to ghcr.io packages). Skip on cache HIT — the tag content is
156-
# already up to date, no point re-uploading the same bytes. Skip if
175+
# access to ghcr.io packages). Skip on exact cache HIT — the tag content
176+
# is already up to date, no point re-uploading the same bytes. Skip if
157177
# neither oras install path succeeded (graceful degradation).
158-
if: github.event_name == 'push' && github.repository == 'PlotJuggler/plotjuggler_core' && env.CONAN_CACHE_HIT != '1' && (steps.setup_oras.outcome == 'success' || steps.setup_oras_fallback.outcome == 'success')
178+
if: github.event_name == 'push' && github.repository == 'PlotJuggler/plotjuggler_core' && env.CONAN_CACHE_EXACT_HIT != '1' && (steps.setup_oras.outcome == 'success' || steps.setup_oras_fallback.outcome == 'success')
159179
shell: bash
160180
run: |
161181
set -euo pipefail
@@ -175,11 +195,26 @@ jobs:
175195
(
176196
cd "${RUNNER_TEMP}"
177197
oras push "${tag}" "conan-cache.tar.zst:application/vnd.oci.image.layer.v1.tar+zstd"
198+
if [ "${GITHUB_REF_NAME}" = "main" ]; then
199+
oras push "${{ steps.conan_cache.outputs.main_tag }}" "conan-cache.tar.zst:application/vnd.oci.image.layer.v1.tar+zstd"
200+
fi
178201
)
179202
203+
- name: Configure
204+
run: >
205+
cmake -S . -B build -G Ninja
206+
-DCMAKE_TOOLCHAIN_FILE=${{ github.workspace }}/build/conan_toolchain.cmake
207+
-DCMAKE_BUILD_TYPE=RelWithDebInfo
208+
-DCMAKE_C_COMPILER_LAUNCHER=ccache
209+
-DCMAKE_CXX_COMPILER_LAUNCHER=ccache
210+
-DPJ_BUILD_PARQUET_IMPORT_EXAMPLE=OFF
211+
212+
- name: Build
213+
run: cmake --build build
214+
180215
- name: ccache stats
181216
if: always()
182217
run: ccache -s
183218

184219
- name: Test
185-
run: xvfb-run -a ./test.sh
220+
run: xvfb-run -a ctest --test-dir build --output-on-failure

.github/workflows/release.yml

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
name: Release
2+
3+
# Triggers on tag push (e.g. `git push origin v0.2.0`). Builds the Conan
4+
# package, uploads it to the project's Cloudsmith remote, and publishes a
5+
# GitHub Release with auto-generated notes.
6+
#
7+
# Secrets required (set in repo Settings → Secrets and variables → Actions):
8+
# CLOUDSMITH_USER — Cloudsmith username (e.g. "davide-faconti")
9+
# CLOUDSMITH_API_KEY — Cloudsmith API key with write access to the
10+
# plotjuggler/plotjuggler repository
11+
#
12+
# Manual trigger: workflow_dispatch lets you re-run for an existing tag if
13+
# the first attempt failed (e.g. flaky upload). Set `tag` input to e.g. v0.1.0.
14+
15+
on:
16+
push:
17+
tags:
18+
- 'v*'
19+
workflow_dispatch:
20+
inputs:
21+
tag:
22+
description: 'Tag to (re-)release (e.g. v0.1.0). Must already exist.'
23+
required: true
24+
25+
concurrency:
26+
group: release-${{ github.ref }}
27+
cancel-in-progress: false # never cancel an in-flight release
28+
29+
jobs:
30+
release:
31+
runs-on: ubuntu-22.04
32+
permissions:
33+
contents: write # required to create the GitHub Release
34+
steps:
35+
- name: Resolve ref
36+
id: ref
37+
run: |
38+
if [[ -n "${{ inputs.tag }}" ]]; then
39+
echo "ref=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
40+
echo "tag=${{ inputs.tag }}" >> "$GITHUB_OUTPUT"
41+
else
42+
echo "ref=${GITHUB_REF}" >> "$GITHUB_OUTPUT"
43+
echo "tag=${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"
44+
fi
45+
# Strip leading 'v' for the Conan version (v0.1.0 -> 0.1.0).
46+
version="${GITHUB_REF_NAME#v}"
47+
version="${version:-${{ inputs.tag }}}"
48+
version="${version#v}"
49+
echo "version=${version}" >> "$GITHUB_OUTPUT"
50+
51+
- uses: actions/checkout@v4
52+
with:
53+
ref: ${{ steps.ref.outputs.ref }}
54+
55+
- uses: conan-io/setup-conan@v1
56+
57+
- name: Detect Conan profile
58+
run: |
59+
conan profile detect --force
60+
conan profile show
61+
62+
- name: Verify recipe version matches tag
63+
# Prevents the common footgun of tagging vX.Y.Z but forgetting to bump
64+
# `version` in conanfile.py. Fails fast before we publish.
65+
run: |
66+
recipe_version=$(python3 -c "import re; print(re.search(r'^\s*version\s*=\s*\"([^\"]+)\"', open('conanfile.py').read(), re.M).group(1))")
67+
if [[ "${recipe_version}" != "${{ steps.ref.outputs.version }}" ]]; then
68+
echo "::error::conanfile.py version (${recipe_version}) does not match tag (${{ steps.ref.outputs.version }})"
69+
exit 1
70+
fi
71+
echo "Version match: ${recipe_version}"
72+
73+
- name: Build Conan package
74+
run: |
75+
conan create . \
76+
--build=missing \
77+
-s build_type=Release \
78+
-s compiler.cppstd=20
79+
80+
- name: Configure Cloudsmith remote
81+
env:
82+
CLOUDSMITH_USER: ${{ secrets.CLOUDSMITH_USER }}
83+
CLOUDSMITH_API_KEY: ${{ secrets.CLOUDSMITH_API_KEY }}
84+
run: |
85+
if [[ -z "$CLOUDSMITH_USER" || -z "$CLOUDSMITH_API_KEY" ]]; then
86+
echo "::error::CLOUDSMITH_USER / CLOUDSMITH_API_KEY secrets not configured"
87+
exit 1
88+
fi
89+
# `add ... --force` makes the step idempotent across re-runs.
90+
conan remote add plotjuggler-cloudsmith https://conan.cloudsmith.io/plotjuggler/plotjuggler --force
91+
conan remote login plotjuggler-cloudsmith "$CLOUDSMITH_USER" -p "$CLOUDSMITH_API_KEY"
92+
93+
- name: Upload package to Cloudsmith
94+
run: |
95+
conan upload "plotjuggler_core/${{ steps.ref.outputs.version }}" \
96+
-r plotjuggler-cloudsmith \
97+
--confirm \
98+
--check
99+
100+
- name: Create GitHub Release
101+
# softprops/action-gh-release: handles auto-generated notes + idempotent
102+
# re-runs (skips if a release for the tag already exists).
103+
uses: softprops/action-gh-release@v2
104+
with:
105+
tag_name: ${{ steps.ref.outputs.tag }}
106+
name: plotjuggler_core ${{ steps.ref.outputs.tag }}
107+
generate_release_notes: true
108+
body: |
109+
## Install via Conan
110+
111+
```bash
112+
conan remote add plotjuggler https://conan.cloudsmith.io/plotjuggler/plotjuggler
113+
```
114+
115+
Add to your `conanfile.py` / `conanfile.txt`:
116+
117+
```python
118+
requires = ("plotjuggler_core/${{ steps.ref.outputs.version }}",)
119+
```
120+
121+
Link in CMake:
122+
123+
```cmake
124+
find_package(plotjuggler_core REQUIRED COMPONENTS plugin_sdk)
125+
target_link_libraries(my_plugin PRIVATE plotjuggler_core::plugin_sdk)
126+
```
127+
128+
See [README.md](https://github.com/PlotJuggler/plotjuggler_core/blob/main/README.md)
129+
for available components (`base`, `datastore`, `plugin_sdk`, `plugin_host`)
130+
and consumer examples.

.github/workflows/windows-ci.yml

Lines changed: 37 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,14 @@ jobs:
7474
id: conan_cache
7575
shell: pwsh
7676
run: |
77-
$hash = (Get-FileHash conanfile.txt -Algorithm SHA256).Hash.Substring(0, 16).ToLower()
77+
$hash = (Get-FileHash conanfile.py -Algorithm SHA256).Hash.Substring(0, 16).ToLower()
7878
$owner = "${{ github.repository_owner }}".ToLower()
7979
$tag = "ghcr.io/$owner/plotjuggler-core-conan-cache:windows-msvc-$hash"
80+
$mainTag = "ghcr.io/$owner/plotjuggler-core-conan-cache:windows-msvc-main"
8081
"tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
82+
"main_tag=$mainTag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append
8183
Write-Host "Cache tag: $tag"
84+
Write-Host "Main fallback cache tag: $mainTag"
8285
8386
- name: Login to ghcr.io
8487
# Required for both pull (private packages) and push. GITHUB_TOKEN is
@@ -95,24 +98,35 @@ jobs:
9598
if: github.repository_owner == 'PlotJuggler' && (steps.setup_oras.outcome == 'success' || steps.setup_oras_fallback.outcome == 'success')
9699
shell: pwsh
97100
run: |
98-
$tag = "${{ steps.conan_cache.outputs.tag }}"
101+
$exactTag = "${{ steps.conan_cache.outputs.tag }}"
102+
$mainTag = "${{ steps.conan_cache.outputs.main_tag }}"
99103
$pullDir = Join-Path $env:RUNNER_TEMP "conan-cache-pull"
100104
New-Item -ItemType Directory -Force -Path $pullDir | Out-Null
101-
Push-Location $pullDir
102-
try {
103-
oras pull $tag 2>&1 | Out-Host
104-
$pulled = $LASTEXITCODE
105-
} finally {
106-
Pop-Location
107-
}
108105
$tarball = Join-Path $pullDir "conan-cache.tar.zst"
109-
if ($pulled -eq 0 -and (Test-Path $tarball)) {
110-
Write-Host "Cache HIT: extracting $tarball"
111-
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.conan2" | Out-Null
112-
tar --zstd -xf $tarball -C $env:USERPROFILE
113-
"CONAN_CACHE_HIT=1" | Out-File -FilePath $env:GITHUB_ENV -Append
114-
} else {
115-
Write-Host "Cache MISS for $tag (cold start or first build of this conanfile)"
106+
107+
foreach ($tag in @($exactTag, $mainTag)) {
108+
Remove-Item $tarball -Force -ErrorAction SilentlyContinue
109+
Write-Host "Trying Conan cache: $tag"
110+
Push-Location $pullDir
111+
try {
112+
oras pull $tag 2>&1 | Out-Host
113+
$pulled = $LASTEXITCODE
114+
} finally {
115+
Pop-Location
116+
}
117+
if ($pulled -eq 0 -and (Test-Path $tarball)) {
118+
Write-Host "Cache HIT: extracting $tarball"
119+
New-Item -ItemType Directory -Force -Path "$env:USERPROFILE\.conan2" | Out-Null
120+
tar --zstd -xf $tarball -C $env:USERPROFILE
121+
"CONAN_CACHE_HIT=1" | Out-File -FilePath $env:GITHUB_ENV -Append
122+
"CONAN_CACHE_HIT_TAG=$tag" | Out-File -FilePath $env:GITHUB_ENV -Append
123+
if ($tag -eq $exactTag) {
124+
"CONAN_CACHE_EXACT_HIT=1" | Out-File -FilePath $env:GITHUB_ENV -Append
125+
}
126+
$global:LASTEXITCODE = 0
127+
exit 0
128+
}
129+
Write-Host "Cache MISS for $tag"
116130
}
117131
# Reset exit code so a missing tag doesn't fail the step
118132
$global:LASTEXITCODE = 0
@@ -124,13 +138,14 @@ jobs:
124138
run: >
125139
conan install . --output-folder=build --build=missing
126140
-s build_type=Release -s compiler.cppstd=20
141+
-o "plotjuggler_core/*:with_tests=True"
127142
128143
- name: Save Conan cache to ghcr.io
129144
# Only push from the canonical repo on real pushes (forks lack write
130-
# access to ghcr.io packages). Skip on cache HIT — the tag content is
131-
# already up to date, no point re-uploading the same bytes. Skip if
145+
# access to ghcr.io packages). Skip on exact cache HIT — the tag content
146+
# is already up to date, no point re-uploading the same bytes. Skip if
132147
# neither oras install path succeeded (graceful degradation).
133-
if: github.event_name == 'push' && github.repository == 'PlotJuggler/plotjuggler_core' && env.CONAN_CACHE_HIT != '1' && (steps.setup_oras.outcome == 'success' || steps.setup_oras_fallback.outcome == 'success')
148+
if: github.event_name == 'push' && github.repository == 'PlotJuggler/plotjuggler_core' && env.CONAN_CACHE_EXACT_HIT != '1' && (steps.setup_oras.outcome == 'success' || steps.setup_oras_fallback.outcome == 'success')
134149
shell: pwsh
135150
run: |
136151
$tag = "${{ steps.conan_cache.outputs.tag }}"
@@ -149,6 +164,9 @@ jobs:
149164
Push-Location $env:RUNNER_TEMP
150165
try {
151166
oras push $tag "conan-cache.tar.zst:application/vnd.oci.image.layer.v1.tar+zstd"
167+
if ($env:GITHUB_REF_NAME -eq "main") {
168+
oras push "${{ steps.conan_cache.outputs.main_tag }}" "conan-cache.tar.zst:application/vnd.oci.image.layer.v1.tar+zstd"
169+
}
152170
} finally {
153171
Pop-Location
154172
}

0 commit comments

Comments
 (0)