diff --git a/CMakeLists.txt b/CMakeLists.txt index a733b12..6e7a09e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 @@ -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() diff --git a/packaging/conan-center-index/README.md b/packaging/conan-center-index/README.md new file mode 100644 index 0000000..37f485d --- /dev/null +++ b/packaging/conan-center-index/README.md @@ -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. diff --git a/packaging/conan-center-index/recipes/plotjuggler_sdk/all/conandata.yml b/packaging/conan-center-index/recipes/plotjuggler_sdk/all/conandata.yml new file mode 100644 index 0000000..cdca00d --- /dev/null +++ b/packaging/conan-center-index/recipes/plotjuggler_sdk/all/conandata.yml @@ -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" diff --git a/packaging/conan-center-index/recipes/plotjuggler_sdk/all/conanfile.py b/packaging/conan-center-index/recipes/plotjuggler_sdk/all/conanfile.py new file mode 100644 index 0000000..54fe468 --- /dev/null +++ b/packaging/conan-center-index/recipes/plotjuggler_sdk/all/conanfile.py @@ -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"] diff --git a/packaging/conan-center-index/recipes/plotjuggler_sdk/all/test_package/CMakeLists.txt b/packaging/conan-center-index/recipes/plotjuggler_sdk/all/test_package/CMakeLists.txt new file mode 100644 index 0000000..ab417d9 --- /dev/null +++ b/packaging/conan-center-index/recipes/plotjuggler_sdk/all/test_package/CMakeLists.txt @@ -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() diff --git a/packaging/conan-center-index/recipes/plotjuggler_sdk/all/test_package/conanfile.py b/packaging/conan-center-index/recipes/plotjuggler_sdk/all/test_package/conanfile.py new file mode 100644 index 0000000..1c56201 --- /dev/null +++ b/packaging/conan-center-index/recipes/plotjuggler_sdk/all/test_package/conanfile.py @@ -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") diff --git a/packaging/conan-center-index/recipes/plotjuggler_sdk/all/test_package/test_package.cpp b/packaging/conan-center-index/recipes/plotjuggler_sdk/all/test_package/test_package.cpp new file mode 100644 index 0000000..6c0eaac --- /dev/null +++ b/packaging/conan-center-index/recipes/plotjuggler_sdk/all/test_package/test_package.cpp @@ -0,0 +1,27 @@ +#include +#include + +#ifdef PJ_TEST_WITH_HOST +#include +#endif + +// parseNumber dispatches to PJ::detail::parseDoubleImpl, which is +// compiled out-of-line into libpj_base.a. Calling it therefore verifies that +// the static archive is actually linked through the plugin_sdk component, not +// merely that the headers are on the include path. +int main() { + const auto value = PJ::parseNumber("1.5"); + if (!value || *value != 1.5) { + return EXIT_FAILURE; + } + +#ifdef PJ_TEST_WITH_HOST + // Constructing an empty catalog (default, disk-less) forces the plugin_host + // archives — and the dlopen-backed loader, hence libdl — onto the link line, + // validating the plugin_host component's lib list and system_libs wiring. + const PJ::PluginRuntimeCatalog catalog; + (void)catalog; +#endif + + return EXIT_SUCCESS; +} diff --git a/packaging/conan-center-index/recipes/plotjuggler_sdk/config.yml b/packaging/conan-center-index/recipes/plotjuggler_sdk/config.yml new file mode 100644 index 0000000..c7af7f7 --- /dev/null +++ b/packaging/conan-center-index/recipes/plotjuggler_sdk/config.yml @@ -0,0 +1,3 @@ +versions: + "0.14.0": + folder: all