Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 16 additions & 8 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ option(PJ_INSTALL_SDK "Install plotjuggler_sdk CMake package (base/plugin_sdk/pl
option(PJ_BUILD_PORTED_PLUGINS "Build pj_ported_plugins (ported plugins collection)" ON)
option(PJ_BUILD_TESTS "Build tests, benchmarks, and examples" ON)
option(PJ_ENABLE_ABI_CHECK "Enable abidiff-based ABI drift gate (requires libabigail)" OFF)
# -Werror is on for our own builds/CI, but must be off for third-party
# packaging (Conan Center, distro builds) where an unpredicted compiler version
# may emit new warnings that would otherwise fail an otherwise-fine build.
option(PJ_WARNINGS_AS_ERRORS "Treat compiler warnings as errors (-Werror / /WX)" ON)

# ---------------------------------------------------------------------------
# Compiler warnings
Expand All @@ -29,18 +33,22 @@ if(MSVC)
# /Zc:preprocessor: conformant preprocessor — required so __VA_ARGS__ inside
# nested macro calls (e.g. PJ_DIALOG_PLUGIN's overload-by-arg-count idiom)
# splits on commas instead of being passed as a single token.
set(PJ_WARNING_FLAGS /W4 /WX /permissive- /Zc:preprocessor)
set(PJ_WARNING_FLAGS /W4 /permissive- /Zc:preprocessor)
if(PJ_WARNINGS_AS_ERRORS)
list(APPEND PJ_WARNING_FLAGS /WX)
endif()
else()
set(PJ_WARNING_FLAGS
-Wall -Wextra -Werror -Wshadow -Wnon-virtual-dtor -Wold-style-cast
-Wall -Wextra -Wshadow -Wnon-virtual-dtor -Wold-style-cast
-Wcast-qual -Wconversion -Woverloaded-virtual -Wpedantic
)
# On wasm32 size_t is 32-bit, so many uint64_t->size_t conversions trip
# -Wshorten-64-to-32 / -Wsign-conversion that never fire on 64-bit desktop
# (see SUSTAINABILITY.md T0-3). Keep the warnings visible but don't fail the
# build for the WASM prototype. TODO: make the ABI/size types 32-bit-clean.
if(EMSCRIPTEN)
list(APPEND PJ_WARNING_FLAGS -Wno-error)
# -Werror only when explicitly requested (default ON, see option above) and
# never on wasm32: there size_t is 32-bit, so many uint64_t->size_t
# conversions trip -Wshorten-64-to-32 / -Wsign-conversion that never fire on
# 64-bit desktop (see SUSTAINABILITY.md T0-3). TODO: make the ABI/size types
# 32-bit-clean, then wasm can opt back into -Werror.
if(PJ_WARNINGS_AS_ERRORS AND NOT EMSCRIPTEN)
list(APPEND PJ_WARNING_FLAGS -Werror)
endif()
endif()

Expand Down
75 changes: 75 additions & 0 deletions packaging/conan-center-index/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Conan Center submission for `plotjuggler_sdk`

These files are the **Conan Center Index (CCI) recipe** for this SDK. They do
**not** belong to the plotjuggler_sdk build — they are staged here so the recipe
is version-controlled and kept in sync with the source. To publish, copy the
`recipes/plotjuggler_sdk/` tree into a fork of
[`conan-io/conan-center-index`](https://github.com/conan-io/conan-center-index)
and open a PR.

```
recipes/plotjuggler_sdk/
├── config.yml # version -> folder map
└── all/
├── conanfile.py # the recipe (downloads a released tag, no vendored sources)
├── conandata.yml # tag url + sha256 per version
└── test_package/ # minimal consumer CCI builds to validate the package
```

This is separate from the repo's own `conanfile.py`, which vendors the working
tree for local dev/consume. The CCI recipe instead downloads the released
**tag tarball** by URL + sha256 and never sees your working tree.

## Prerequisites before submitting

1. **Ship the `-Werror` gate in a released tag.** The recipe sets
`-DPJ_WARNINGS_AS_ERRORS=OFF` so a not-yet-pinned compiler on CCI's build
matrix cannot fail the build on a new warning. That option was added on this
branch (`CMakeLists.txt`). It must be present in the tag CCI consumes:
merge this branch, cut a new release tag, then update `config.yml` +
`conandata.yml` (new version + new sha256). The sha256 currently pinned is
for the *pre-gate* `v0.14.0` tag and is only good for local smoke testing.
2. **Pick the entry version.** Per the SDK's own versioning note, the first
public release carrying the unified `pj.data_processors.v1` service must be
`1.0.0`. Decide whether CCI is seeded with `0.14.x` or `1.0.0`.
3. **Confirm dependencies exist in CCI** (verified 2026-07-01, all present):
`nlohmann_json/3.12.0`, `fmt/12.1.0`, `fast_float/8.1.0`.

## Test locally before opening the PR

From inside a checkout of the conan-center-index fork:

```bash
cd recipes/plotjuggler_sdk
# Build + run the test_package for the default profile:
conan create all --version=0.14.0 --build=missing
# Exercise the option matrix CCI will build:
conan create all --version=0.14.0 -o "&:with_host=False" --build=missing
conan create all --version=0.14.0 -o "&:assert_throws=True" --build=missing
conan create all --version=0.14.0 -s compiler.cppstd=20 --build=missing
```

Then run the Conan Center hooks locally (catches KB-Hxxx violations the reviewer
bot will flag):

```bash
conan config install https://github.com/conan-io/conan-center-index.git \
-sf .c3i/hooks -tf .conan2/extensions/hooks # one-time
conan create all --version=0.14.0 --build=missing # hooks run inline
```

## Open the PR

Fork `conan-io/conan-center-index`, drop this tree under `recipes/`, commit, and
open a PR titled `plotjuggler_sdk/0.14.0`. Expect one or more review rounds —
the usual notes are around option count (`with_host`, `assert_throws`), license
packaging, and the compiler-minimum map. Acceptance is at CCI maintainer
discretion.

## Recurring cost per release

Each future SDK release needs a follow-up CCI PR: add the new version to
`config.yml`, add its `url` + `sha256` to `conandata.yml`. Weigh this against the
Cloudsmith Conan remote (`conan.cloudsmith.io/plotjuggler/plotjuggler`) you
already operate, which needs no external review but requires consumers to add
the remote.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
sources:
# NOTE: this sha256 is for the v0.14.0 tag *as it exists today*, which does
# NOT yet contain the PJ_WARNINGS_AS_ERRORS gate (added on the conan-center
# branch). Before submitting to Conan Center, cut a release tag that includes
# that CMake change and replace both the version (here + config.yml) and the
# sha256 below. Recompute with:
# curl -sL https://github.com/PlotJuggler/plotjuggler_sdk/archive/refs/tags/vX.Y.Z.tar.gz | sha256sum
"0.14.0":
url: "https://github.com/PlotJuggler/plotjuggler_sdk/archive/refs/tags/v0.14.0.tar.gz"
sha256: "49aecfdc5ddf46930e56a5ee4843a1435c3fb8e47b1a4ab0289fd9646dc1986d"
193 changes: 193 additions & 0 deletions packaging/conan-center-index/recipes/plotjuggler_sdk/all/conanfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
import os

from conan import ConanFile
from conan.errors import ConanInvalidConfiguration
from conan.tools.build import check_min_cppstd
from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout
from conan.tools.files import copy, get, rm
from conan.tools.microsoft import is_msvc
from conan.tools.scm import Version

required_conan_version = ">=2.1"


class PlotjugglerSdkConan(ConanFile):
name = "plotjuggler_sdk"
description = (
"C++20 foundation libraries for PlotJuggler: plugin SDK and plugin host loaders."
)
license = "Apache-2.0"
url = "https://github.com/conan-io/conan-center-index"
homepage = "https://github.com/PlotJuggler/plotjuggler_sdk"
topics = ("plotjuggler", "plugin-sdk", "telemetry", "data-visualization")
package_type = "static-library"
settings = "os", "arch", "compiler", "build_type"

# The SDK ships as static archives only (host loaders + vocabulary types);
# there is intentionally no `shared` option. There is deliberately no `fPIC`
# option either: PlotJuggler plugins are shared objects and the host loaders
# dlopen() them, so every target hardcodes POSITION_INDEPENDENT_CODE ON
# upstream. Exposing fPIC would be a no-op knob (CCI requires options to have
# an observable effect). `with_host` lets consumers that only author plugins
# skip the host-side loader libraries. `assert_throws` switches PJ_ASSERT
# from assert() to throwing.
options = {
"with_host": [True, False],
"assert_throws": [True, False],
}
default_options = {
"with_host": True,
"assert_throws": False,
}

@property
def _min_cppstd(self):
return 20

@property
def _compilers_minimum_version(self):
# Tune these against the oldest toolchains you actually intend to
# support; these are conservative C++20 baselines.
return {
"gcc": "11",
"clang": "14",
"apple-clang": "14",
"msvc": "192",
"Visual Studio": "16",
}

def layout(self):
cmake_layout(self, src_folder="src")

def requirements(self):
# nlohmann_json is in PUBLIC installed headers (config_envelope.hpp,
# widget_data, plugin_catalog) and is find_dependency()'d by the
# installed CMake usage, so it must propagate to consumers.
self.requires("nlohmann_json/3.12.0", transitive_headers=True)

# fmt and fast_float are header-only, private implementation details:
# they never appear in an installed pj_base header and are never linked
# into the archives' public surface. Keep them invisible so they do not
# propagate to downstream consumers of this static-library package.
self.requires(
"fmt/12.1.0",
headers=True,
libs=False,
visible=False,
transitive_headers=False,
transitive_libs=False,
)
self.requires(
"fast_float/8.1.0",
headers=True,
libs=False,
visible=False,
transitive_headers=False,
transitive_libs=False,
)

def validate(self):
if self.settings.compiler.cppstd:
check_min_cppstd(self, self._min_cppstd)
minimum_version = self._compilers_minimum_version.get(str(self.settings.compiler))
if minimum_version and Version(self.settings.compiler.version) < minimum_version:
raise ConanInvalidConfiguration(
f"{self.ref} requires C++{self._min_cppstd}, which needs at least "
f"{self.settings.compiler} {minimum_version} "
f"(got {self.settings.compiler.version})."
)

def source(self):
get(self, **self.conan_data["sources"][self.version], strip_root=True)

def generate(self):
tc = CMakeToolchain(self)
tc.cache_variables["PJ_INSTALL_SDK"] = True
tc.cache_variables["PJ_BUILD_TESTS"] = False
tc.cache_variables["PJ_BUILD_PORTED_PLUGINS"] = False
# Never fail a third-party package build on a warning from a compiler
# version upstream has not pinned. Requires the PJ_WARNINGS_AS_ERRORS
# option (SDK >= the release that added it).
tc.cache_variables["PJ_WARNINGS_AS_ERRORS"] = False
tc.cache_variables["PJ_ASSERT_THROWS"] = bool(self.options.assert_throws)
tc.generate()

deps = CMakeDeps(self)
deps.generate()

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def package(self):
copy(
self,
"LICENSE*",
src=self.source_folder,
dst=os.path.join(self.package_folder, "licenses"),
)
cmake = CMake(self)
cmake.install()

# Conan Center re-derives the CMake package from package_info() below, so
# the project-installed find/config/targets files must be removed. Keep
# PjPluginManifest.cmake — it is a genuine build helper shipped as a
# cmake_build_module, not a generated find script.
cmake_dir = os.path.join(self.package_folder, "lib", "cmake", "plotjuggler_sdk")
rm(self, "plotjuggler_sdkConfig*.cmake", cmake_dir)
rm(self, "plotjuggler_sdkTargets*.cmake", cmake_dir)

def package_info(self):
self.cpp_info.set_property("cmake_file_name", "plotjuggler_sdk")

# Ship the PjPluginManifest helper as a build module so consumers can
# call pj_emit_plugin_manifest() after find_package() returns.
self.cpp_info.set_property(
"cmake_build_modules",
[os.path.join("lib", "cmake", "plotjuggler_sdk", "PjPluginManifest.cmake")],
)

# --- base ---
base = self.cpp_info.components["base"]
base.set_property("cmake_target_name", "plotjuggler_sdk::base")
base.libs = ["pj_base"]
base.includedirs = ["include"]
# assert.hpp is a header that branches on PJ_ASSERT_THROWS at the
# *consumer's* compile time; upstream exports it PUBLIC on pj_base. The
# define must ride along or consumers get assert() while the archive was
# built to throw (and vice versa). Propagated to plugin_sdk/plugin_host
# via their `requires = ["base", ...]`.
if self.options.assert_throws:
base.defines = ["PJ_ASSERT_THROWS"]

# --- plugin_sdk (umbrella INTERFACE: pj_base + nlohmann_json) ---
sdk = self.cpp_info.components["plugin_sdk"]
sdk.set_property("cmake_target_name", "plotjuggler_sdk::plugin_sdk")
sdk.libs = [] # INTERFACE only
sdk.includedirs = ["include"]
sdk.requires = ["base", "nlohmann_json::nlohmann_json"]
# PJ_DIALOG_PLUGIN's variadic-overload macro needs conformant __VA_ARGS__
# expansion on MSVC. Upstream sets this as an INTERFACE option on the
# dialog SDK target, which is dropped when we strip the project's CMake
# targets — restore it here so MSVC dialog-plugin authors still compile.
if is_msvc(self):
sdk.cxxflags = ["/Zc:preprocessor"]

# --- plugin_host (umbrella linking every host-side loader) ---
if self.options.with_host:
host = self.cpp_info.components["plugin_host"]
host.set_property("cmake_target_name", "plotjuggler_sdk::plugin_host")
host.libs = [
"pj_plugin_runtime_catalog",
"pj_data_source_host",
"pj_message_parser_host",
"pj_toolbox_host",
"pj_dialog_library",
"pj_plugin_catalog",
"pj_plugin_loader_detail",
]
host.includedirs = ["include"]
host.requires = ["base", "nlohmann_json::nlohmann_json"]
if self.settings.os in ("Linux", "FreeBSD"):
host.system_libs = ["dl"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
cmake_minimum_required(VERSION 3.22)
project(test_package LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(plotjuggler_sdk REQUIRED CONFIG)

add_executable(test_package test_package.cpp)
# plugin_sdk transitively pulls in the `base` component (libpj_base.a) and
# nlohmann_json, exercising real linkage — not just header availability.
target_link_libraries(test_package PRIVATE plotjuggler_sdk::plugin_sdk)

# When the package was built with_host (default), also link and exercise the
# host loaders so the plugin_host component's lib list and its `dl` syslib
# wiring are validated. Gated so `-o with_host=False` still builds.
if(TEST_WITH_HOST)
target_link_libraries(test_package PRIVATE plotjuggler_sdk::plugin_host)
target_compile_definitions(test_package PRIVATE PJ_TEST_WITH_HOST)
endif()
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import os

from conan import ConanFile
from conan.tools.build import can_run
from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain, cmake_layout


class TestPackageConan(ConanFile):
settings = "os", "arch", "compiler", "build_type"
test_type = "explicit"

def requirements(self):
self.requires(self.tested_reference_str)

def layout(self):
cmake_layout(self)

def generate(self):
# Mirror the tested package's with_host option: the plugin_host
# component only exists when the package was built with it, so the
# consumer must only link plotjuggler_sdk::plugin_host in that case.
with_host = bool(self.dependencies["plotjuggler_sdk"].options.with_host)
tc = CMakeToolchain(self)
tc.cache_variables["TEST_WITH_HOST"] = with_host
tc.generate()
CMakeDeps(self).generate()

def build(self):
cmake = CMake(self)
cmake.configure()
cmake.build()

def test(self):
if can_run(self):
bin_path = os.path.join(self.cpp.build.bindir, "test_package")
self.run(bin_path, env="conanrun")
Loading
Loading