diff --git a/.github/workflows/linux-ci.yml b/.github/workflows/linux-ci.yml index fe88233..050d0a9 100644 --- a/.github/workflows/linux-ci.yml +++ b/.github/workflows/linux-ci.yml @@ -72,10 +72,11 @@ jobs: shell: bash run: | set -euo pipefail - # Hash every conanfile.txt under the tree (top-level today, - # pj_ported_plugins/ in the near future) so the key invalidates - # whenever any of them changes. - hash=$(find . -name conanfile.txt -not -path './build/*' -not -path './.git/*' \ + # Hash every conanfile.{py,txt} under the tree so the key invalidates + # whenever any of them changes. The top-level is now conanfile.py + # (Conan recipe); subtrees like pj_ported_plugins/ still use .txt. + hash=$(find . \( -name 'conanfile.py' -o -name 'conanfile.txt' \) \ + -not -path './build/*' -not -path './.git/*' \ -print0 | sort -z | xargs -0 sha256sum | sha256sum | cut -c1-16) owner="${GITHUB_REPOSITORY_OWNER,,}" # Build-type suffix leaves room for a future debug-asan variant diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..feb0037 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,130 @@ +name: Release + +# Triggers on tag push (e.g. `git push origin v0.2.0`). Builds the Conan +# package, uploads it to the project's Cloudsmith remote, and publishes a +# GitHub Release with auto-generated notes. +# +# 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). Set `tag` input to e.g. v0.1.0. + +on: + push: + tags: + - 'v*' + workflow_dispatch: + inputs: + tag: + description: 'Tag to (re-)release (e.g. v0.1.0). Must already exist.' + required: true + +concurrency: + group: release-${{ github.ref }} + cancel-in-progress: false # never cancel an in-flight release + +jobs: + release: + runs-on: ubuntu-22.04 + permissions: + contents: write # required to create the GitHub Release + 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: Detect Conan profile + run: | + conan profile detect --force + conan profile show + + - 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: Build Conan package + 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 + run: | + conan upload "plotjuggler_core/${{ steps.ref.outputs.version }}" \ + -r plotjuggler-cloudsmith \ + --confirm \ + --check + + - 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: ${{ steps.ref.outputs.tag }} + name: plotjuggler_core ${{ steps.ref.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_core/${{ steps.ref.outputs.version }}",) + ``` + + Link in CMake: + + ```cmake + find_package(plotjuggler_core REQUIRED COMPONENTS plugin_sdk) + target_link_libraries(my_plugin PRIVATE plotjuggler_core::plugin_sdk) + ``` + + See [README.md](https://github.com/PlotJuggler/plotjuggler_core/blob/main/README.md) + for available components (`base`, `datastore`, `plugin_sdk`, `plugin_host`) + and consumer examples. diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index 6890840..f135abd 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -74,7 +74,7 @@ jobs: id: conan_cache shell: pwsh run: | - $hash = (Get-FileHash conanfile.txt -Algorithm SHA256).Hash.Substring(0, 16).ToLower() + $hash = (Get-FileHash conanfile.py -Algorithm SHA256).Hash.Substring(0, 16).ToLower() $owner = "${{ github.repository_owner }}".ToLower() $tag = "ghcr.io/$owner/plotjuggler-core-conan-cache:windows-msvc-$hash" "tag=$tag" | Out-File -FilePath $env:GITHUB_OUTPUT -Append diff --git a/CMakeLists.txt b/CMakeLists.txt index f69d921..490e92e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -16,7 +16,7 @@ include(PjPluginManifest) option(PJ_ASSERT_THROWS "Use exceptions instead of assert() for PJ_ASSERT" OFF) option(PJ_ENABLE_SANITIZERS "Enable ASAN for Debug builds" OFF) option(PJ_ENABLE_TSAN "Enable ThreadSanitizer for Debug builds" OFF) -option(PJ_INSTALL_SDK "Install PlotJugglerSDK package (DataSource + Dialog)" OFF) +option(PJ_INSTALL_SDK "Install plotjuggler_core CMake package (base/datastore/plugin_sdk/plugin_host)" OFF) option( PJ_BUILD_PARQUET_IMPORT_EXAMPLE "Build parquet_import example (requires full Arrow C++ and Parquet)" @@ -166,36 +166,45 @@ if(PJ_BUILD_PORTED_PLUGINS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/pj_ported_plu endif() # --------------------------------------------------------------------------- -# PlotJugglerSDK package install +# plotjuggler_core package install +# +# Exported CMake namespace: plotjuggler_core:: +# Components: +# base — vocabulary types (always available) +# datastore — columnar engine (optional, requires PJ_BUILD_DATASTORE) +# plugin_sdk — plugin-author surface: base + dialog SDK + parser SDK +# plugin_host — host-side loaders (data_source, message_parser, toolbox, +# dialog, catalogs) # --------------------------------------------------------------------------- if(PJ_INSTALL_SDK) include(CMakePackageConfigHelpers) - set(PJ_SDK_VERSION "1.0.0") - set(PJ_SDK_CMAKE_DIR lib/cmake/PlotJugglerSDK) + set(PJ_PACKAGE_VERSION "0.1.0") + set(PJ_PACKAGE_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/plotjuggler_core) - install(EXPORT PlotJugglerSDKTargets - NAMESPACE PlotJugglerSDK:: - DESTINATION ${PJ_SDK_CMAKE_DIR} + install(EXPORT plotjuggler_coreTargets + NAMESPACE plotjuggler_core:: + DESTINATION ${PJ_PACKAGE_CMAKE_DIR} ) configure_package_config_file( - cmake/PlotJugglerSDKConfig.cmake.in - "${CMAKE_CURRENT_BINARY_DIR}/PlotJugglerSDKConfig.cmake" - INSTALL_DESTINATION ${PJ_SDK_CMAKE_DIR} + cmake/plotjuggler_coreConfig.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/plotjuggler_coreConfig.cmake" + INSTALL_DESTINATION ${PJ_PACKAGE_CMAKE_DIR} ) write_basic_package_version_file( - "${CMAKE_CURRENT_BINARY_DIR}/PlotJugglerSDKConfigVersion.cmake" - VERSION ${PJ_SDK_VERSION} + "${CMAKE_CURRENT_BINARY_DIR}/plotjuggler_coreConfigVersion.cmake" + VERSION ${PJ_PACKAGE_VERSION} COMPATIBILITY SameMajorVersion ) install(FILES - "${CMAKE_CURRENT_BINARY_DIR}/PlotJugglerSDKConfig.cmake" - "${CMAKE_CURRENT_BINARY_DIR}/PlotJugglerSDKConfigVersion.cmake" - DESTINATION ${PJ_SDK_CMAKE_DIR} + "${CMAKE_CURRENT_BINARY_DIR}/plotjuggler_coreConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/plotjuggler_coreConfigVersion.cmake" + cmake/PjPluginManifest.cmake + DESTINATION ${PJ_PACKAGE_CMAKE_DIR} ) endif() diff --git a/build.sh b/build.sh index b54f897..19411bd 100755 --- a/build.sh +++ b/build.sh @@ -69,6 +69,8 @@ build_config() { conan install "$SCRIPT_DIR" --output-folder="$build_dir" --build=missing \ -s build_type="$build_type" -s compiler.cppstd=20 \ + -o "plotjuggler_core/*:with_tests=True" \ + -o "plotjuggler_core/*:with_parquet_example=True" \ "${conan_extra[@]+"${conan_extra[@]}"}" # Install dependencies from subdirectory conanfiles (e.g. pj_ported_plugins) diff --git a/cmake/PjPluginManifest.cmake b/cmake/PjPluginManifest.cmake index a92bf94..be5b709 100644 --- a/cmake/PjPluginManifest.cmake +++ b/cmake/PjPluginManifest.cmake @@ -22,6 +22,8 @@ # Writes /.pjmanifest.json next to the DSO and installs it # alongside the DSO. +include(GNUInstallDirs) # CMAKE_INSTALL_LIBDIR — must be defined before install(FILES ...) + function(pj_emit_plugin_manifest TARGET) set(_options) set(_oneValueArgs FAMILY MANIFEST_FILE ABI_MAJOR) diff --git a/cmake/PlotJugglerSDKConfig.cmake.in b/cmake/PlotJugglerSDKConfig.cmake.in deleted file mode 100644 index 385e473..0000000 --- a/cmake/PlotJugglerSDKConfig.cmake.in +++ /dev/null @@ -1,32 +0,0 @@ -@PACKAGE_INIT@ - -include(CMakeFindDependencyMacro) - -# Default to DataSource if no components requested. -if(NOT PlotJugglerSDK_FIND_COMPONENTS) - set(PlotJugglerSDK_FIND_COMPONENTS DataSource) -endif() - -# Include the exported targets (defines PlotJugglerSDK::DataSource, etc.). -include("${CMAKE_CURRENT_LIST_DIR}/PlotJugglerSDKTargets.cmake") - -# Per-component validation. -foreach(_comp ${PlotJugglerSDK_FIND_COMPONENTS}) - if(_comp STREQUAL "DataSource") - set(PlotJugglerSDK_DataSource_FOUND TRUE) - - elseif(_comp STREQUAL "Dialog") - find_dependency(nlohmann_json) - if(nlohmann_json_FOUND) - set(PlotJugglerSDK_Dialog_FOUND TRUE) - endif() - - else() - set(PlotJugglerSDK_${_comp}_FOUND FALSE) - if(PlotJugglerSDK_FIND_REQUIRED_${_comp}) - message(FATAL_ERROR "PlotJugglerSDK: unknown component \"${_comp}\"") - endif() - endif() -endforeach() - -check_required_components(PlotJugglerSDK) diff --git a/cmake/plotjuggler_coreConfig.cmake.in b/cmake/plotjuggler_coreConfig.cmake.in new file mode 100644 index 0000000..b139366 --- /dev/null +++ b/cmake/plotjuggler_coreConfig.cmake.in @@ -0,0 +1,44 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# Default to plugin_sdk if no components requested — the most common +# consumer (a plugin author) wants the plugin-author SDK surface. +if(NOT plotjuggler_core_FIND_COMPONENTS) + set(plotjuggler_core_FIND_COMPONENTS plugin_sdk) +endif() + +# Include the exported targets (defines plotjuggler_core::base, etc.). +include("${CMAKE_CURRENT_LIST_DIR}/plotjuggler_coreTargets.cmake") + +# Per-component dependencies and validation. +foreach(_comp ${plotjuggler_core_FIND_COMPONENTS}) + if(_comp STREQUAL "base") + set(plotjuggler_core_base_FOUND TRUE) + + elseif(_comp STREQUAL "datastore") + find_dependency(fmt) + find_dependency(tsl-robin-map) + find_dependency(nanoarrow) + set(plotjuggler_core_datastore_FOUND TRUE) + + elseif(_comp STREQUAL "plugin_sdk") + find_dependency(nlohmann_json) + # Ship the cmake/PjPluginManifest.cmake helper so plugin authors can call + # pj_emit_plugin_manifest() without copying the helper into their tree. + include("${CMAKE_CURRENT_LIST_DIR}/PjPluginManifest.cmake") + set(plotjuggler_core_plugin_sdk_FOUND TRUE) + + elseif(_comp STREQUAL "plugin_host") + find_dependency(nlohmann_json) + set(plotjuggler_core_plugin_host_FOUND TRUE) + + else() + set(plotjuggler_core_${_comp}_FOUND FALSE) + if(plotjuggler_core_FIND_REQUIRED_${_comp}) + message(FATAL_ERROR "plotjuggler_core: unknown component \"${_comp}\"") + endif() + endif() +endforeach() + +check_required_components(plotjuggler_core) diff --git a/conanfile.py b/conanfile.py new file mode 100644 index 0000000..b62837c --- /dev/null +++ b/conanfile.py @@ -0,0 +1,180 @@ +"""Conan 2 recipe for plotjuggler_core. + +Exposes four CMake components under the `plotjuggler_core::` namespace: + + base — pj_base, vocabulary types (always available) + datastore — pj_datastore, columnar engine (option: with_datastore) + plugin_sdk — umbrella for plugin authors (base + dialog SDK + parser SDK) + plugin_host — umbrella for host loaders (data_source/parser/toolbox/dialog) + +A consuming Conan recipe declares e.g. `plotjuggler_core/0.1.0` and then: + + find_package(plotjuggler_core REQUIRED COMPONENTS plugin_sdk) + target_link_libraries(my_plugin PRIVATE plotjuggler_core::plugin_sdk) + +The `plugin_sdk` component also ships `PjPluginManifest.cmake`, so authors can +call `pj_emit_plugin_manifest()` without copying the helper into their tree. + +Local development (build.sh) uses this same recipe with `with_tests=True` so +gtest/benchmark/arrow are resolved as test_requires. +""" + +from conan import ConanFile +from conan.tools.cmake import CMake, CMakeDeps, CMakeToolchain +from conan.tools.files import copy +import os + + +class PlotjugglerCoreConan(ConanFile): + name = "plotjuggler_core" + version = "0.1.0" + license = "MIT" + url = "https://github.com/PlotJuggler/plotjuggler_core" + description = "C++20 foundation libraries for PlotJuggler: storage engine, plugin SDK, plugin host loaders." + topics = ("plotjuggler", "plugin-sdk", "telemetry", "data-visualization") + package_type = "static-library" + + settings = "os", "compiler", "build_type", "arch" + options = { + "fPIC": [True, False], + "with_datastore": [True, False], + "with_host": [True, False], + "with_tests": [True, False], + "with_parquet_example": [True, False], + "assert_throws": [True, False], + } + default_options = { + "fPIC": True, + "with_datastore": True, + "with_host": True, + "with_tests": False, + "with_parquet_example": False, + "assert_throws": False, + # Arrow build flags (only resolved when with_parquet_example=True). + "arrow/*:parquet": True, + "arrow/*:with_snappy": True, + # pj_datastore's Arrow IPC import path needs nanoarrow_ipc + flatcc. + "nanoarrow/*:with_ipc": True, + # Boost is pulled in transitively by arrow. without_cobalt avoids a + # known upstream packaging error in recent boost recipes; without_test + # trims unneeded modules. + "boost/*:without_test": True, + "boost/*:without_cobalt": True, + } + + exports_sources = ( + "CMakeLists.txt", + "LICENSE", + "cmake/*", + "pj_base/*", + "pj_datastore/*", + "pj_plugins/*", + "examples/*", + ) + + def config_options(self): + if self.settings.os == "Windows": + del self.options.fPIC + + def requirements(self): + # nlohmann_json appears in public headers (widget_data, plugin_catalog). + self.requires("nlohmann_json/3.12.0", transitive_headers=True) + + if self.options.with_datastore: + # fmt + tsl-robin-map are private; nanoarrow is in public headers. + self.requires("fmt/12.1.0") + self.requires("tsl-robin-map/1.4.0", transitive_headers=True) + self.requires("nanoarrow/0.7.0", transitive_headers=True) + + def build_requirements(self): + # Tests + benchmarks + parquet example are local-dev only. Consumers + # of plotjuggler_core never see these. + if self.options.with_tests: + self.test_requires("gtest/1.17.0") + if self.options.with_datastore: + self.test_requires("benchmark/1.9.4") + if self.options.with_parquet_example: + self.test_requires("arrow/23.0.1") + + def generate(self): + deps = CMakeDeps(self) + deps.generate() + + tc = CMakeToolchain(self) + tc.cache_variables["PJ_INSTALL_SDK"] = True + tc.cache_variables["PJ_BUILD_DATASTORE"] = bool(self.options.with_datastore) + tc.cache_variables["PJ_BUILD_TESTS"] = bool(self.options.with_tests) + tc.cache_variables["PJ_BUILD_PORTED_PLUGINS"] = False + tc.cache_variables["PJ_BUILD_PARQUET_IMPORT_EXAMPLE"] = bool( + self.options.with_parquet_example + ) + tc.cache_variables["PJ_ASSERT_THROWS"] = bool(self.options.assert_throws) + tc.generate() + + def build(self): + cmake = CMake(self) + cmake.configure() + cmake.build() + + def package(self): + cmake = CMake(self) + cmake.install() + copy( + self, + "LICENSE", + src=self.source_folder, + dst=os.path.join(self.package_folder, "licenses"), + ) + + def package_info(self): + self.cpp_info.set_property("cmake_file_name", "plotjuggler_core") + # No top-level umbrella target: the four components have + # mutually-exclusive audiences. Consumers must request a component. + + # --- base --- + base = self.cpp_info.components["base"] + base.set_property("cmake_target_name", "plotjuggler_core::base") + base.libs = ["pj_base"] + base.includedirs = ["include"] + + # --- datastore (optional) --- + if self.options.with_datastore: + ds = self.cpp_info.components["datastore"] + ds.set_property("cmake_target_name", "plotjuggler_core::datastore") + ds.libs = ["pj_datastore"] + ds.includedirs = ["include"] + ds.requires = [ + "base", + "fmt::fmt", + "tsl-robin-map::tsl-robin-map", + "nanoarrow::nanoarrow", + ] + + # --- plugin_sdk (umbrella INTERFACE: pj_base + pj_dialog_sdk) --- + sdk = self.cpp_info.components["plugin_sdk"] + sdk.set_property("cmake_target_name", "plotjuggler_core::plugin_sdk") + sdk.libs = [] # INTERFACE only + sdk.includedirs = ["include"] + sdk.requires = ["base", "nlohmann_json::nlohmann_json"] + # Ship the cmake helper alongside the component. cmake_build_modules + # makes Conan auto-include() it after find_package() succeeds. + sdk.set_property("cmake_build_modules", [ + os.path.join("lib", "cmake", "plotjuggler_core", "PjPluginManifest.cmake"), + ]) + + # --- 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_core::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", + ] + host.includedirs = ["include"] + host.requires = ["base", "nlohmann_json::nlohmann_json"] + if self.settings.os in ("Linux", "FreeBSD"): + host.system_libs = ["dl"] diff --git a/conanfile.txt b/conanfile.txt deleted file mode 100644 index 410e4b8..0000000 --- a/conanfile.txt +++ /dev/null @@ -1,19 +0,0 @@ -[requires] -fmt/12.1.0 -tsl-robin-map/1.4.0 -gtest/1.17.0 -benchmark/1.9.4 -arrow/23.0.1 -nanoarrow/0.7.0 -nlohmann_json/3.12.0 - -[options] -arrow/*:parquet=True -arrow/*:with_snappy=True -nanoarrow/*:with_ipc=True -boost/*:without_test=True -boost/*:without_cobalt=True - -[generators] -CMakeDeps -CMakeToolchain diff --git a/examples/sdk_consumer/CMakeLists.txt b/examples/sdk_consumer/CMakeLists.txt index 0ce2041..4516c81 100644 --- a/examples/sdk_consumer/CMakeLists.txt +++ b/examples/sdk_consumer/CMakeLists.txt @@ -1,7 +1,15 @@ cmake_minimum_required(VERSION 3.22) project(sdk_consumer LANGUAGES CXX) +set(CMAKE_CXX_STANDARD 20) +set(CMAKE_CXX_STANDARD_REQUIRED ON) -find_package(PlotJugglerSDK REQUIRED COMPONENTS DataSource) +find_package(plotjuggler_core REQUIRED COMPONENTS plugin_sdk) add_library(minimal_data_source SHARED minimal_data_source.cpp) -target_link_libraries(minimal_data_source PRIVATE PlotJugglerSDK::DataSource) +target_link_libraries(minimal_data_source PRIVATE plotjuggler_core::plugin_sdk) + +# Exercise the cmake helper shipped alongside plugin_sdk. +pj_emit_plugin_manifest(minimal_data_source + FAMILY data_source + MANIFEST_FILE ${CMAKE_CURRENT_SOURCE_DIR}/manifest.json +) diff --git a/examples/sdk_consumer/manifest.json b/examples/sdk_consumer/manifest.json new file mode 100644 index 0000000..ea99f06 --- /dev/null +++ b/examples/sdk_consumer/manifest.json @@ -0,0 +1,6 @@ +{ + "id": "minimal-data-source", + "name": "Minimal Data Source", + "version": "0.1.0", + "description": "Smoke-test plugin for the installed plotjuggler_core SDK." +} diff --git a/pj_base/CMakeLists.txt b/pj_base/CMakeLists.txt index 05e7a05..64d22dd 100644 --- a/pj_base/CMakeLists.txt +++ b/pj_base/CMakeLists.txt @@ -17,7 +17,7 @@ target_compile_options(pj_base PRIVATE ) set_target_properties(pj_base PROPERTIES POSITION_INDEPENDENT_CODE ON - EXPORT_NAME DataSource + EXPORT_NAME base ) if(PJ_ASSERT_THROWS) target_compile_definitions(pj_base PUBLIC PJ_ASSERT_THROWS) @@ -28,11 +28,11 @@ endif() # --------------------------------------------------------------------------- if(PJ_INSTALL_SDK) - install(TARGETS pj_base EXPORT PlotJugglerSDKTargets - ARCHIVE DESTINATION lib - LIBRARY DESTINATION lib + install(TARGETS pj_base EXPORT plotjuggler_coreTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) - install(DIRECTORY include/ DESTINATION include) + install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) endif() # --------------------------------------------------------------------------- diff --git a/pj_datastore/CMakeLists.txt b/pj_datastore/CMakeLists.txt index 89b4843..f2df510 100644 --- a/pj_datastore/CMakeLists.txt +++ b/pj_datastore/CMakeLists.txt @@ -21,11 +21,18 @@ add_library(pj_datastore STATIC src/colormap_registry_host.cpp src/object_store.cpp ) -target_include_directories(pj_datastore PUBLIC include) +target_include_directories(pj_datastore PUBLIC + $ + $ +) target_compile_features(pj_datastore PUBLIC cxx_std_20) target_compile_options(pj_datastore PRIVATE ${PJ_WARNING_FLAGS} ${PJ_SANITIZER_FLAGS} ) +set_target_properties(pj_datastore PROPERTIES + POSITION_INDEPENDENT_CODE ON + EXPORT_NAME datastore +) if(PJ_ASSERT_THROWS) target_compile_definitions(pj_datastore PUBLIC PJ_ASSERT_THROWS) endif() @@ -39,6 +46,18 @@ target_link_libraries(pj_datastore ${PJ_NANOARROW_IPC_TARGET} ) +# --------------------------------------------------------------------------- +# Install (guarded by PJ_INSTALL_SDK in root CMakeLists.txt) +# --------------------------------------------------------------------------- + +if(PJ_INSTALL_SDK) + install(TARGETS pj_datastore EXPORT plotjuggler_coreTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) + install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +endif() + # --------------------------------------------------------------------------- # Tests # --------------------------------------------------------------------------- diff --git a/pj_plugins/CMakeLists.txt b/pj_plugins/CMakeLists.txt index 64055ca..c685fc1 100644 --- a/pj_plugins/CMakeLists.txt +++ b/pj_plugins/CMakeLists.txt @@ -1,15 +1,19 @@ add_subdirectory(dialog_protocol) # --------------------------------------------------------------------------- -# pj_plugin_sdk — INTERFACE library that exposes the C++ SDK headers -# (MessageParserPluginBase, ObjectIngestPolicy, parser trampolines, ...) -# and the dependencies they need. Plugins link this to get access to the SDK -# without having to repeat the include paths. +# pj_plugin_sdk — umbrella INTERFACE library that exposes the full plugin-author +# SDK surface: C++ SDK headers (MessageParserPluginBase, ObjectIngestPolicy, +# parser trampolines, ...) PLUS the dialog SDK (widget_data, widget_event) so +# plugin authors can link one target and get everything they need. # --------------------------------------------------------------------------- add_library(pj_plugin_sdk INTERFACE) -target_include_directories(pj_plugin_sdk INTERFACE include) -target_link_libraries(pj_plugin_sdk INTERFACE pj_base) +target_include_directories(pj_plugin_sdk INTERFACE + $ + $ +) +target_link_libraries(pj_plugin_sdk INTERFACE pj_base pj_dialog_sdk) +set_target_properties(pj_plugin_sdk PROPERTIES EXPORT_NAME plugin_sdk) # --------------------------------------------------------------------------- # pj_plugin_catalog — host-side embedded-manifest discovery @@ -20,9 +24,16 @@ find_package(nlohmann_json REQUIRED) add_library(pj_plugin_catalog STATIC src/plugin_catalog.cpp ) -target_include_directories(pj_plugin_catalog PUBLIC include) +target_include_directories(pj_plugin_catalog PUBLIC + $ + $ +) target_compile_features(pj_plugin_catalog PUBLIC cxx_std_20) target_compile_options(pj_plugin_catalog PRIVATE ${PJ_WARNING_FLAGS}) +set_target_properties(pj_plugin_catalog PROPERTIES + POSITION_INDEPENDENT_CODE ON + EXPORT_NAME plugin_catalog +) target_link_libraries(pj_plugin_catalog PUBLIC pj_base @@ -39,9 +50,16 @@ target_link_libraries(pj_plugin_catalog add_library(pj_data_source_host STATIC src/data_source_library.cpp ) -target_include_directories(pj_data_source_host PUBLIC include) +target_include_directories(pj_data_source_host PUBLIC + $ + $ +) target_compile_features(pj_data_source_host PUBLIC cxx_std_20) target_compile_options(pj_data_source_host PRIVATE ${PJ_WARNING_FLAGS}) +set_target_properties(pj_data_source_host PROPERTIES + POSITION_INDEPENDENT_CODE ON + EXPORT_NAME data_source_host +) target_link_libraries(pj_data_source_host PUBLIC pj_base @@ -104,9 +122,16 @@ endif() # PJ_BUILD_TESTS add_library(pj_message_parser_host STATIC src/message_parser_library.cpp ) -target_include_directories(pj_message_parser_host PUBLIC include) +target_include_directories(pj_message_parser_host PUBLIC + $ + $ +) target_compile_features(pj_message_parser_host PUBLIC cxx_std_20) target_compile_options(pj_message_parser_host PRIVATE ${PJ_WARNING_FLAGS}) +set_target_properties(pj_message_parser_host PROPERTIES + POSITION_INDEPENDENT_CODE ON + EXPORT_NAME message_parser_host +) target_link_libraries(pj_message_parser_host PUBLIC pj_base @@ -144,9 +169,16 @@ endif() # PJ_BUILD_TESTS add_library(pj_toolbox_host STATIC src/toolbox_library.cpp ) -target_include_directories(pj_toolbox_host PUBLIC include) +target_include_directories(pj_toolbox_host PUBLIC + $ + $ +) target_compile_features(pj_toolbox_host PUBLIC cxx_std_20) target_compile_options(pj_toolbox_host PRIVATE ${PJ_WARNING_FLAGS}) +set_target_properties(pj_toolbox_host PROPERTIES + POSITION_INDEPENDENT_CODE ON + EXPORT_NAME toolbox_host +) target_link_libraries(pj_toolbox_host PUBLIC pj_base @@ -162,9 +194,16 @@ target_link_libraries(pj_toolbox_host add_library(pj_plugin_runtime_catalog STATIC src/plugin_runtime_catalog.cpp ) -target_include_directories(pj_plugin_runtime_catalog PUBLIC include) +target_include_directories(pj_plugin_runtime_catalog PUBLIC + $ + $ +) target_compile_features(pj_plugin_runtime_catalog PUBLIC cxx_std_20) target_compile_options(pj_plugin_runtime_catalog PRIVATE ${PJ_WARNING_FLAGS}) +set_target_properties(pj_plugin_runtime_catalog PROPERTIES + POSITION_INDEPENDENT_CODE ON + EXPORT_NAME plugin_runtime_catalog +) target_link_libraries(pj_plugin_runtime_catalog PUBLIC pj_data_source_host @@ -174,6 +213,23 @@ target_link_libraries(pj_plugin_runtime_catalog pj_base ) +# --------------------------------------------------------------------------- +# pj_plugin_host — umbrella INTERFACE that bundles every host-side loader. +# Hosts (the PlotJuggler app, test rigs) link this single target to get +# discovery + all four plugin-family loaders + the runtime catalog. +# --------------------------------------------------------------------------- + +add_library(pj_plugin_host INTERFACE) +target_link_libraries(pj_plugin_host INTERFACE + pj_plugin_runtime_catalog + pj_data_source_host + pj_message_parser_host + pj_toolbox_host + pj_dialog_library + pj_plugin_catalog +) +set_target_properties(pj_plugin_host PROPERTIES EXPORT_NAME plugin_host) + if(PJ_BUILD_TESTS) # --------------------------------------------------------------------------- @@ -292,3 +348,23 @@ add_dependencies(plugin_catalog_test mock_data_source_plugin mock_json_parser_pl add_test(NAME plugin_catalog_test COMMAND plugin_catalog_test) endif() # PJ_BUILD_TESTS + +# --------------------------------------------------------------------------- +# Install (guarded by PJ_INSTALL_SDK in root CMakeLists.txt) +# --------------------------------------------------------------------------- + +if(PJ_INSTALL_SDK) + install(TARGETS + pj_plugin_sdk + pj_plugin_catalog + pj_data_source_host + pj_message_parser_host + pj_toolbox_host + pj_plugin_runtime_catalog + pj_plugin_host + EXPORT plotjuggler_coreTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ) + install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +endif() diff --git a/pj_plugins/dialog_protocol/CMakeLists.txt b/pj_plugins/dialog_protocol/CMakeLists.txt index 09390db..d0765f4 100644 --- a/pj_plugins/dialog_protocol/CMakeLists.txt +++ b/pj_plugins/dialog_protocol/CMakeLists.txt @@ -24,12 +24,16 @@ target_include_directories(pj_dialog_sdk INTERFACE $ $ ) -set_target_properties(pj_dialog_sdk PROPERTIES EXPORT_NAME Dialog) +set_target_properties(pj_dialog_sdk PROPERTIES EXPORT_NAME dialog_sdk) # Host-side C++ API (depends on protocol header + nlohmann/json) add_library(pj_dialog_host INTERFACE) target_link_libraries(pj_dialog_host INTERFACE pj_dialog_protocol nlohmann_json::nlohmann_json) -target_include_directories(pj_dialog_host INTERFACE include) +target_include_directories(pj_dialog_host INTERFACE + $ + $ +) +set_target_properties(pj_dialog_host PROPERTIES EXPORT_NAME dialog_host) # --------------------------------------------------------------------------- # pj_dialog_library — host-side dialog .so loader @@ -38,9 +42,16 @@ target_include_directories(pj_dialog_host INTERFACE include) add_library(pj_dialog_library STATIC src/dialog_library.cpp ) -target_include_directories(pj_dialog_library PUBLIC include) +target_include_directories(pj_dialog_library PUBLIC + $ + $ +) target_compile_features(pj_dialog_library PUBLIC cxx_std_20) target_compile_options(pj_dialog_library PRIVATE ${PJ_WARNING_FLAGS}) +set_target_properties(pj_dialog_library PROPERTIES + POSITION_INDEPENDENT_CODE ON + EXPORT_NAME dialog_library +) target_link_libraries(pj_dialog_library PUBLIC pj_dialog_host @@ -124,11 +135,17 @@ endif() # PJ_BUILD_TESTS # --- Install (guarded by PJ_INSTALL_SDK in root CMakeLists.txt) --- if(PJ_INSTALL_SDK) - install(TARGETS pj_dialog_protocol pj_dialog_sdk EXPORT PlotJugglerSDKTargets) - install(FILES include/pj_plugins/dialog_protocol.h - DESTINATION include/pj_plugins - ) - install(DIRECTORY include/pj_plugins/sdk/ - DESTINATION include/pj_plugins/sdk + install(TARGETS + pj_dialog_protocol + pj_dialog_sdk + pj_dialog_host + pj_dialog_library + EXPORT plotjuggler_coreTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} ) + # All dialog_protocol headers live under include/pj_plugins/ (dialog_protocol.h, + # sdk/, host/). Install the whole tree — it merges with pj_plugins/include/ + # at the destination since each module writes into distinct files. + install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) endif() diff --git a/test_sdk_install.sh b/test_sdk_install.sh index 3d09645..f92eaba 100755 --- a/test_sdk_install.sh +++ b/test_sdk_install.sh @@ -11,7 +11,7 @@ cleanup() { } trap cleanup EXIT -echo "=== SDK Install Test ===" +echo "=== plotjuggler_core install test ===" echo "Staging dir: ${STAGING_DIR}" echo "" @@ -28,6 +28,9 @@ cmake -S "$SCRIPT_DIR" -B "$BUILD_DIR" \ -DCMAKE_TOOLCHAIN_FILE="$BUILD_DIR/conan_toolchain.cmake" \ -DCMAKE_BUILD_TYPE=Release \ -DPJ_INSTALL_SDK=ON \ + -DPJ_BUILD_TESTS=OFF \ + -DPJ_BUILD_PORTED_PLUGINS=OFF \ + -DPJ_BUILD_PARQUET_IMPORT_EXAMPLE=OFF \ -DCMAKE_INSTALL_PREFIX="$STAGING_DIR" cmake --build "$BUILD_DIR" -j "$(nproc)" @@ -42,36 +45,79 @@ echo "--- Step 2: Install to staging ---" cmake --install "$BUILD_DIR" echo "" -echo "Installed files:" -find "$STAGING_DIR" -type f | sort +echo "Installed CMake package files:" +find "$STAGING_DIR" -path '*/cmake/plotjuggler_core*' | sort + +echo "" +echo "Installed libraries:" +find "$STAGING_DIR" -name 'libpj_*' | sort # --------------------------------------------------------------------------- -# 3. Build the out-of-tree consumer against the installed SDK +# 3. Build the out-of-tree consumer against the installed package # --------------------------------------------------------------------------- echo "" echo "--- Step 3: Configure + build sdk_consumer example ---" cmake -S "$SCRIPT_DIR/examples/sdk_consumer" -B "$CONSUMER_BUILD_DIR" \ - -DCMAKE_PREFIX_PATH="$STAGING_DIR" \ + -DCMAKE_PREFIX_PATH="$STAGING_DIR;$BUILD_DIR" \ -DCMAKE_BUILD_TYPE=Release cmake --build "$CONSUMER_BUILD_DIR" -j "$(nproc)" +# --------------------------------------------------------------------------- +# 4. Verify each public component is independently findable +# --------------------------------------------------------------------------- + +echo "" +echo "--- Step 4: Smoke-test find_package COMPONENTS ---" + +for comp in base plugin_sdk plugin_host datastore; do + COMP_DIR="$(mktemp -d)" + cat > "$COMP_DIR/CMakeLists.txt" </dev/null 2>&1; then + echo " OK plotjuggler_core::${comp}" + else + echo " FAIL plotjuggler_core::${comp}" + cmake -S "$COMP_DIR" -B "$COMP_DIR/build" \ + -DCMAKE_PREFIX_PATH="$STAGING_DIR;$BUILD_DIR" \ + -DCMAKE_BUILD_TYPE=Release + rm -rf "$COMP_DIR" + exit 1 + fi + rm -rf "$COMP_DIR" +done + +# --------------------------------------------------------------------------- +# 5. Verify no host-internal headers leaked +# --------------------------------------------------------------------------- + echo "" -echo "--- Step 4: Verify no host-internal headers leaked ---" +echo "--- Step 5: Verify installed-header sanity ---" +# Nothing source-tree-private should appear under include/. tests/ and +# examples/ directories from the module source trees must NOT be exported. +# (The pj_plugins/testing/ headers ARE intentionally public test helpers.) LEAKED=0 -for pattern in 'pj_datastore/'; do - if grep -r "$pattern" "$STAGING_DIR/include/" 2>/dev/null; then - echo "ERROR: Found leaked dependency: ${pattern}" +for forbidden_dir in /tests/ /examples/ /src/; do + if find "$STAGING_DIR/include" -path "*${forbidden_dir}*" -type f 2>/dev/null | grep -q .; then + echo "ERROR: Found forbidden directory '${forbidden_dir}' in installed include/" + find "$STAGING_DIR/include" -path "*${forbidden_dir}*" -type f LEAKED=1 fi done if [[ $LEAKED -ne 0 ]]; then - echo "FAIL: Installed headers reference host-internal dependencies." exit 1 fi echo "" -echo "=== SDK Install Test PASSED ===" +echo "=== plotjuggler_core install test PASSED ==="