From 2c83ba34d4e47c72acbe1d93946443b41fc92958 Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 14 Aug 2026 20:09:40 -0500 Subject: [PATCH 1/4] feat(lib): add CMake install/export for host C++ library (find_package + FetchContent) Give the cross-platform host C++ library a proper, modern CMake package so a separate project can consume it two ways with the SAME link name espp::espp: - find_package(espp REQUIRED) from an installed tree, and - FetchContent / CPM add_subdirectory(lib) from the build tree. Changes: - espp_pc gets target-based usage requirements: every include dir espp.cmake collects is exposed as $, plus $, and cxx_std_23 + system link deps (pthread / ws2_32,winmm,iphlpapi) are PUBLIC so they propagate to consumers. - Namespaced ALIAS espp::espp for build-tree consumers, and EXPORT_NAME espp so the installed/exported target is also espp::espp (archive stays libespp_pc.a). - New espp_install_cmake_package() (in espp.cmake) installs the target via EXPORT esppTargets, the public headers (merged flat into /include, mirroring lib/pc/include), esppTargets.cmake, and generated esppConfig.cmake / esppConfigVersion.cmake (SameMajorVersion) under lib/cmake/espp. All third-party deps are vendored (headers installed + objects in the .a); the only non-bundled PUBLIC dep is Threads, resolved via find_dependency(Threads) in the config. - Gate the python bindings behind option ESPP_BUILD_PYTHON (default OFF) so a plain `cmake -S lib` yields just the C++ lib + install/export with no pybind11 dependency. build.sh / build.ps1 pass -DESPP_BUILD_PYTHON=ON to preserve their behavior (this is what build_libraries CI publishes to lib/pc); the scikit-build wheel path (SKBUILD) is unaffected. The legacy lib/pc install (used by the pc/ test build and CI artifacts) is kept unchanged. Co-Authored-By: Claude Opus 4.8 --- lib/CMakeLists.txt | 81 +++++++++++++++++++++++++++-------- lib/build.ps1 | 4 +- lib/build.sh | 4 +- lib/cmake/esppConfig.cmake.in | 15 +++++++ lib/espp.cmake | 52 ++++++++++++++++++++++ 5 files changed, 135 insertions(+), 21 deletions(-) create mode 100644 lib/cmake/esppConfig.cmake.in diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 7bfd5ba42..bc8801ec0 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -3,21 +3,33 @@ cmake_minimum_required(VERSION 3.20) # building PC c++ library and python binding message(STATUS "Building for PC: C++ & Python") -project(espp) +project(espp VERSION 1.1.8) + +# Build the espp Python bindings (_espp)? Default OFF so a plain +# `cmake -S lib` produces just the C++ static library + its install/export +# (find_package(espp) / espp::espp) with no pybind11 dependency. The standalone +# scripts (build.sh / build.ps1) pass -DESPP_BUILD_PYTHON=ON to also build and +# install the python package into lib/pc (this is what CI publishes). Wheel +# builds via scikit-build-core take the SKBUILD path below regardless of this +# option. +option(ESPP_BUILD_PYTHON "Build the espp Python bindings module (_espp)" OFF) # Prefer an installed pybind11 (provided as a build requirement when building # python wheels via pip / scikit-build-core); fall back to fetching it with CPM -# for standalone CMake builds (e.g. ./build.sh). -find_package(pybind11 CONFIG QUIET) -if(NOT pybind11_FOUND) - include(cmake/CPM.cmake) - CPMAddPackage( - NAME pybind11 - GIT_REPOSITORY https://github.com/pybind/pybind11.git - VERSION 3.0.4 - ) - if(NOT pybind11_ADDED) - message(FATAL_ERROR "pybind11 not found. Please ensure it is available in the specified version.") +# for standalone CMake builds (e.g. ./build.sh). Only needed when the python +# bindings are actually being built. +if(SKBUILD OR ESPP_BUILD_PYTHON) + find_package(pybind11 CONFIG QUIET) + if(NOT pybind11_FOUND) + include(cmake/CPM.cmake) + CPMAddPackage( + NAME pybind11 + GIT_REPOSITORY https://github.com/pybind/pybind11.git + VERSION 3.0.4 + ) + if(NOT pybind11_ADDED) + message(FATAL_ERROR "pybind11 not found. Please ensure it is available in the specified version.") + endif() endif() endif() @@ -61,20 +73,53 @@ else() STATIC # Provides a relative path to your source file(s). ${ESPP_SOURCES} ) + # Namespaced ALIAS so build-tree (FetchContent / CPM add_subdirectory) and + # install-tree (find_package) consumers link the SAME name: espp::espp. + add_library(espp::espp ALIAS ${TARGET_NAME}) + # Export the target as `espp` (not `espp_pc`) so find_package consumers link + # `espp::espp` too -- the SAME name as the build-tree alias above. The on-disk + # archive name stays libespp_pc.a (OUTPUT_NAME unchanged) for pc/ and CI. + set_target_properties(${TARGET_NAME} PROPERTIES EXPORT_NAME espp) set_property(TARGET ${TARGET_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) target_link_options(${TARGET_NAME} PRIVATE "${LINK_ARG}") - target_link_libraries(${TARGET_NAME} ${ESPP_EXTERNAL_LIBS}) + # PUBLIC so the system link deps (pthread, or ws2_32/winmm/iphlpapi on Windows) + # propagate to consumers of the exported target. + target_link_libraries(${TARGET_NAME} PUBLIC ${ESPP_EXTERNAL_LIBS}) if(WIN32) - target_link_libraries(${TARGET_NAME} winmm) + target_link_libraries(${TARGET_NAME} PUBLIC winmm) endif() - target_compile_features(${TARGET_NAME} PRIVATE cxx_std_23) + # PUBLIC/INTERFACE so consumers of espp::espp automatically compile as C++23 + # (the espp headers require it). + target_compile_features(${TARGET_NAME} PUBLIC cxx_std_23) + + # --------------------------------------------------------------------------- + # Usage requirements (modern, target-based). Expose every include dir espp.cmake + # collects as a BUILD_INTERFACE dir (so FetchContent/CPM consumers using the + # build tree get them), and /include as the INSTALL_INTERFACE dir. + # --------------------------------------------------------------------------- + include(GNUInstallDirs) + foreach(_dir IN LISTS ESPP_INCLUDE_DIRS) + target_include_directories(${TARGET_NAME} PUBLIC $) + endforeach() + target_include_directories(${TARGET_NAME} + PUBLIC $) - # install build output and headers + # --------------------------------------------------------------------------- + # Legacy local install into lib/pc (kept for the pc/ test build and for the + # libespp_* CI artifacts, which consume lib/pc/{include,libespp_pc.a}). + # --------------------------------------------------------------------------- install(TARGETS ${TARGET_NAME} ARCHIVE DESTINATION ${PROJECT_SOURCE_DIR}/pc) - espp_install_includes(${PROJECT_SOURCE_DIR}/pc) + # --------------------------------------------------------------------------- + # Proper install + export so `find_package(espp)` works from a separate + # project. Installs into GNUInstallDirs under CMAKE_INSTALL_PREFIX. + # --------------------------------------------------------------------------- + espp_install_cmake_package(${TARGET_NAME}) + # Build and install the python package (espp/ with the _espp extension inside) - espp_install_python_module(${PROJECT_SOURCE_DIR}/pc) + if(ESPP_BUILD_PYTHON) + espp_install_python_module(${PROJECT_SOURCE_DIR}/pc) + endif() endif() diff --git a/lib/build.ps1 b/lib/build.ps1 index 315749b2e..e1e627cbd 100644 --- a/lib/build.ps1 +++ b/lib/build.ps1 @@ -9,8 +9,8 @@ if (-not (Test-Path -Path $buildDir)) { # Change to the build directory Set-Location -Path $buildDir -# Run cmake -cmake .. +# Run cmake (ESPP_BUILD_PYTHON=ON also builds/installs the python package) +cmake -DESPP_BUILD_PYTHON=ON .. # Run cmake --build . --config Release --target install cmake --build . --config Release --target install --parallel 4 diff --git a/lib/build.sh b/lib/build.sh index eff1744e3..6ae1f3615 100755 --- a/lib/build.sh +++ b/lib/build.sh @@ -2,5 +2,7 @@ mkdir build cd build -cmake .. +# ESPP_BUILD_PYTHON=ON builds and installs the python package into lib/pc +# alongside the C++ static library + headers (this is what CI publishes). +cmake -DESPP_BUILD_PYTHON=ON .. cmake --build . --config Release --target install --parallel 4 diff --git a/lib/cmake/esppConfig.cmake.in b/lib/cmake/esppConfig.cmake.in new file mode 100644 index 000000000..269fe0d78 --- /dev/null +++ b/lib/cmake/esppConfig.cmake.in @@ -0,0 +1,15 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +# espp's only non-bundled, PUBLIC runtime dependency is the system threads +# library (pthread on POSIX). Every other third-party library used by espp +# (reflect-cpp, magic_enum, tabulate, fmt, alpaca, cli, csv2, hid-rp, cdr) is +# vendored: its headers are installed under /include and its objects are +# compiled into libespp_pc.a, so no find_dependency is required for those. On +# Windows the system libs (ws2_32, winmm, iphlpapi) resolve automatically. +find_dependency(Threads) + +include("${CMAKE_CURRENT_LIST_DIR}/esppTargets.cmake") + +check_required_components(espp) diff --git a/lib/espp.cmake b/lib/espp.cmake index 7bc03fa38..e104f2b42 100644 --- a/lib/espp.cmake +++ b/lib/espp.cmake @@ -192,6 +192,58 @@ function(espp_install_includes FOLDER) install(DIRECTORY ${ESPP_EXTERNAL_INCLUDES_SEPARATE} DESTINATION ${FOLDER}/include/) endfunction() +# make an espp_install_cmake_package command that gives the C++ library target a +# proper install + export so a separate project can `find_package(espp)` and link +# `espp::espp`. Installs into GNUInstallDirs under CMAKE_INSTALL_PREFIX: +# /lib/libespp_pc.a +# /include/... (all component + vendored headers, flat) +# /lib/cmake/espp/esppTargets.cmake, esppConfig.cmake, ...Version.cmake +# +# All third-party deps (reflect-cpp, magic_enum, tabulate, fmt, alpaca, cli, +# csv2, hid-rp, cdr) are VENDORED: their headers are installed under +# /include and their objects are compiled into libespp_pc.a, so a +# consumer needs no extra find_package for them. The only non-bundled PUBLIC +# dependency is the system threads library (handled via find_dependency(Threads) +# in esppConfig.cmake.in; on Windows the ws2_32/winmm/iphlpapi system libs +# resolve automatically). +function(espp_install_cmake_package TARGET_NAME) + include(GNUInstallDirs) + include(CMakePackageConfigHelpers) + + install(TARGETS ${TARGET_NAME} + EXPORT esppTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + INCLUDES DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + + # Install the public headers the library exposes, merged flat into + # /include (mirrors the historical lib/pc/include layout, so the same + # quote-includes such as "logger.hpp" / "magic_enum.hpp" resolve). A trailing + # slash on the source installs the directory CONTENTS. + foreach(_inc IN LISTS ESPP_INCLUDES ESPP_EXTERNAL_INCLUDES ESPP_EXTERNAL_INCLUDES_SEPARATE) + install(DIRECTORY ${_inc}/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) + endforeach() + + install(EXPORT esppTargets + NAMESPACE espp:: + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/espp + FILE esppTargets.cmake) + + configure_package_config_file( + ${CMAKE_CURRENT_LIST_DIR}/cmake/esppConfig.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/esppConfig.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/espp) + write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/esppConfigVersion.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + install(FILES + ${CMAKE_CURRENT_BINARY_DIR}/esppConfig.cmake + ${CMAKE_CURRENT_BINARY_DIR}/esppConfigVersion.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/espp) +endfunction() + # make an espp_add_python_module command that defines the `_espp` pybind11 # extension module target (the native part of the `espp` python package) function(espp_add_python_module) From 39e4acf483573c4a847da3d6a888fce04fb71f9a Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 14 Aug 2026 22:24:53 -0500 Subject: [PATCH 2/4] fix(lib): gate standard find_package install behind ESPP_INSTALL so build.sh doesn't write /usr/local The build_libraries CI job (build_linux + build_macos) failed because lib/build.sh runs `cmake --build . --target install` with no CMAKE_INSTALL_PREFIX. The newly added standard install/export (espp_install_cmake_package) therefore fired during the plain build.sh install and tried to write the export into the default system prefix (/usr/local), exiting 2. - Add option(ESPP_INSTALL ... OFF). The standard install/export (find_package(espp) / espp::espp) now only runs when ESPP_INSTALL=ON. build.sh leaves it OFF, so it installs ONLY the legacy lib/pc artifacts and never touches /usr/local. A real consumer opts in with `-DESPP_INSTALL=ON -DCMAKE_INSTALL_PREFIX=`. - Guard the legacy lib/pc install (which writes into the espp SOURCE tree) behind `if(PROJECT_IS_TOP_LEVEL OR ESPP_BUILD_PYTHON)` so a FetchContent/CPM consumer's `cmake --install` no longer mutates the espp source tree. Bump cmake_minimum_required to 3.21 for a reliable PROJECT_IS_TOP_LEVEL. - Fix the misleading top-of-file status message: report C++-only by default and only mention Python bindings when SKBUILD/ESPP_BUILD_PYTHON is set. Verified: build.sh succeeds with zero /usr/local install lines and populates lib/pc; `-DESPP_INSTALL=ON` installs a find_package-able package that a separate consumer configures, builds, links (espp::espp), and runs against. Co-Authored-By: Claude Opus 4.8 --- lib/CMakeLists.txt | 46 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 10 deletions(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index bc8801ec0..f7746c1b4 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,8 +1,9 @@ -cmake_minimum_required(VERSION 3.20) - -# building PC c++ library and python binding -message(STATUS "Building for PC: C++ & Python") +# 3.21+ for a reliable PROJECT_IS_TOP_LEVEL (used to guard the source-tree +# lib/pc install so it never fires when espp is consumed via FetchContent/CPM). +cmake_minimum_required(VERSION 3.21) +# Build the PC (host) C++ static library. Python bindings are opt-in (see the +# ESPP_BUILD_PYTHON option below); a plain `cmake -S lib` is C++-only. project(espp VERSION 1.1.8) # Build the espp Python bindings (_espp)? Default OFF so a plain @@ -14,6 +15,21 @@ project(espp VERSION 1.1.8) # option. option(ESPP_BUILD_PYTHON "Build the espp Python bindings module (_espp)" OFF) +# Install the standard, relocatable CMake package (install(TARGETS ... EXPORT), +# esppConfig.cmake, flattened headers) so a separate project can +# `find_package(espp)` and link `espp::espp`. OFF by default so build.sh's plain +# `cmake --build . --target install` (which sets no CMAKE_INSTALL_PREFIX) does +# NOT write the export into the default system prefix (/usr/local). A real +# consumer opts in explicitly: `-DESPP_INSTALL=ON -DCMAKE_INSTALL_PREFIX=`. +option(ESPP_INSTALL "Install espp as a find_package-able package (standard install/export under CMAKE_INSTALL_PREFIX)" OFF) + +# Report what this configure will actually build (Python is opt-in). +if(SKBUILD OR ESPP_BUILD_PYTHON) + message(STATUS "Building espp for PC: C++ static library + Python bindings (_espp)") +else() + message(STATUS "Building espp for PC: C++ static library only (set -DESPP_BUILD_PYTHON=ON for Python bindings)") +endif() + # Prefer an installed pybind11 (provided as a build requirement when building # python wheels via pip / scikit-build-core); fall back to fetching it with CPM # for standalone CMake builds (e.g. ./build.sh). Only needed when the python @@ -106,17 +122,27 @@ else() # --------------------------------------------------------------------------- # Legacy local install into lib/pc (kept for the pc/ test build and for the - # libespp_* CI artifacts, which consume lib/pc/{include,libespp_pc.a}). + # libespp_* CI artifacts, which consume lib/pc/{include,libespp_pc.a}). This + # writes into the espp SOURCE tree, so guard it to the standalone/CI build + # (build.sh, top-level) and the python-package build -- it must NOT fire when + # espp is a subproject and a parent project runs `cmake --install` (that would + # mutate the espp source tree of a FetchContent/CPM consumer). # --------------------------------------------------------------------------- - install(TARGETS ${TARGET_NAME} - ARCHIVE DESTINATION ${PROJECT_SOURCE_DIR}/pc) - espp_install_includes(${PROJECT_SOURCE_DIR}/pc) + if(PROJECT_IS_TOP_LEVEL OR ESPP_BUILD_PYTHON) + install(TARGETS ${TARGET_NAME} + ARCHIVE DESTINATION ${PROJECT_SOURCE_DIR}/pc) + espp_install_includes(${PROJECT_SOURCE_DIR}/pc) + endif() # --------------------------------------------------------------------------- # Proper install + export so `find_package(espp)` works from a separate - # project. Installs into GNUInstallDirs under CMAKE_INSTALL_PREFIX. + # project. Installs into GNUInstallDirs under CMAKE_INSTALL_PREFIX. Opt-in via + # -DESPP_INSTALL=ON so the plain build.sh install (no CMAKE_INSTALL_PREFIX) + # does not write to the default system prefix (/usr/local). # --------------------------------------------------------------------------- - espp_install_cmake_package(${TARGET_NAME}) + if(ESPP_INSTALL) + espp_install_cmake_package(${TARGET_NAME}) + endif() # Build and install the python package (espp/ with the _espp extension inside) if(ESPP_BUILD_PYTHON) From 4674aa28257f914a5fc53baad18733e76a1f71ea Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Fri, 14 Aug 2026 23:48:37 -0500 Subject: [PATCH 3/4] feat(lib): derive CMake version from git tag; pc uses find_package(espp); remove legacy lib/pc install Version: derive project(espp VERSION) from `git describe --tags` at configure time (strip leading 'v', keep MAJOR.MINOR.PATCH), falling back to 0.0.0 for tarball/no-git builds. Flows into esppConfigVersion.cmake via PROJECT_VERSION. The python wheel's version still comes from setuptools_scm (unchanged). Legacy lib/pc removed: drop the source-tree install (install(TARGETS ... ARCHIVE DESTINATION .../pc) + espp_install_includes) and the espp_install_includes helper. The standard install/export (espp_install_cmake_package) is now THE install path; ESPP_INSTALL defaults ON for a top-level build and OFF for a subproject. espp_install_python_module installs the python package under the standard prefix (/espp) instead of lib/pc. pc/ consumes the installed package: pc/CMakeLists.txt now find_package(espp) + links espp::espp (with $ to preserve whole-archive linking so global-ctor/registration code, e.g. the Windows timer-period adjustment, is not stripped). The RTPS limits/fragmentation config (RTPS_CONFIG_HEADER / RTPS_ENABLE_FRAGMENTATION / RTPS_MAX_SAMPLE_SIZE) is now exported as PUBLIC compile definitions on espp::espp so find_package consumers compile the rtps headers with the same ABI as the archive. Build scripts / CI / interop: lib/build.{sh,ps1} install to a gitignored /install staging prefix (ESPP_INSTALL=ON, ESPP_BUILD_PYTHON=ON); pc/build.{sh,ps1} configure with CMAKE_PREFIX_PATH=/install; build_libraries.yml installs to and uploads that prefix; run_interop.sh installs lib to /tmp/espp/install and points pc at it. .gitignore: lib/pc -> /install/. Docs (lib/README.md, pc/README.md, interop README) updated for the new flow. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/build_libraries.yml | 12 ++-- .gitignore | 4 +- components/rtps/interop/README.md | 4 +- components/rtps/interop/run_interop.sh | 12 ++-- lib/CMakeLists.txt | 97 ++++++++++++++++---------- lib/README.md | 57 +++++++++++---- lib/build.ps1 | 32 ++++----- lib/build.sh | 24 +++++-- lib/espp.cmake | 44 +++++++----- pc/CMakeLists.txt | 44 +++++++----- pc/README.md | 17 ++++- pc/build.ps1 | 27 +++---- pc/build.sh | 16 +++-- pyproject.toml | 2 +- 14 files changed, 242 insertions(+), 150 deletions(-) diff --git a/.github/workflows/build_libraries.yml b/.github/workflows/build_libraries.yml index 8cc5645e5..561e71675 100644 --- a/.github/workflows/build_libraries.yml +++ b/.github/workflows/build_libraries.yml @@ -29,17 +29,13 @@ jobs: - name: Build libraries working-directory: lib/ - run: | - mkdir build - cd build - cmake .. - cmake --build . --config Release --target install + run: ./build.ps1 - name: Upload output folder uses: actions/upload-artifact@v7 with: name: libespp_windows - path: lib/pc + path: install build_linux: @@ -67,7 +63,7 @@ jobs: uses: actions/upload-artifact@v7 with: name: libespp_linux - path: lib/pc + path: install build_macos: @@ -100,4 +96,4 @@ jobs: uses: actions/upload-artifact@v7 with: name: libespp_macos - path: lib/pc + path: install diff --git a/.gitignore b/.gitignore index d50af212d..f3de3877c 100644 --- a/.gitignore +++ b/.gitignore @@ -44,7 +44,9 @@ dependencies.lock # weird mac folders... .DS_Store -lib/pc +# local espp staging install prefix produced by lib/build.sh (find_package tree +# + python package); consumed by pc/build.sh via CMAKE_PREFIX_PATH. +/install/ _build/ __pycache__/ managed_components/ diff --git a/components/rtps/interop/README.md b/components/rtps/interop/README.md index e6d08b36e..0ae835e91 100644 --- a/components/rtps/interop/README.md +++ b/components/rtps/interop/README.md @@ -13,7 +13,9 @@ cd components/rtps/interop Requires docker. One container (`ros:jazzy-ros-base` = FastDDS + rmw_fastrtps + `ros2` CLI) runs everything in a single network namespace, so RTPS multicast works unconditionally. The repo is bind-mounted and copied to a container-local tree -before building, so your host `lib/pc` artifacts are never touched. +before building (espp is installed into a container-local staging prefix and the +pc tests `find_package` it from there), so your host build artifacts are never +touched. ## Matrix diff --git a/components/rtps/interop/run_interop.sh b/components/rtps/interop/run_interop.sh index 77a4d5ca1..d10baaa22 100755 --- a/components/rtps/interop/run_interop.sh +++ b/components/rtps/interop/run_interop.sh @@ -6,10 +6,10 @@ # # NOTE: no `set -u` - ROS 2's setup.bash references unset variables. -# Work on a container-local copy: the pc tests link the lib installed into -# lib/pc inside the source tree, which on the bind mount holds the developer's -# host-platform (e.g. macOS) artifacts. Building in-place would either link -# incompatible objects or clobber them with linux ones. +# Work on a container-local copy: the build installs espp into a staging prefix +# (/tmp/espp/install) and the pc tests find_package it from there. Doing this on +# the bind mount would either mix in the developer's host-platform (e.g. macOS) +# artifacts or clobber them with linux ones, so copy to a container-local tree. echo "===== Copy sources to container-local tree =====" rsync -a --delete --exclude '.git/' --exclude 'build/' --exclude 'build-*/' --exclude 'managed_components/' --exclude 'docs/' --exclude 'dependencies.lock' /work/ /tmp/espp/ cd /tmp/espp @@ -23,9 +23,9 @@ result() { # name exit_code } note "Build espp lib + host binaries (linux)" -cmake -S lib -B lib/build -DCMAKE_BUILD_TYPE=Release > /tmp/cmake_lib.log 2>&1 \ +cmake -S lib -B lib/build -DCMAKE_BUILD_TYPE=Release -DESPP_INSTALL=ON -DCMAKE_INSTALL_PREFIX=/tmp/espp/install > /tmp/cmake_lib.log 2>&1 \ && cmake --build lib/build -j"$(nproc)" --target install > /tmp/build_lib.log 2>&1 \ - && cmake -S pc -B pc/build -DCMAKE_BUILD_TYPE=Release > /tmp/cmake.log 2>&1 \ + && cmake -S pc -B pc/build -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/tmp/espp/install > /tmp/cmake.log 2>&1 \ && cmake --build pc/build -j"$(nproc)" --target \ rtps_pubsub rtps_golden rtps_facade_pubsub rtps_typed_pubsub \ rtps_facade_frag rtps_facade_backlog rtps_facade_frag_sizes rtps_service_loopback \ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index f7746c1b4..60e93af65 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -1,27 +1,59 @@ -# 3.21+ for a reliable PROJECT_IS_TOP_LEVEL (used to guard the source-tree -# lib/pc install so it never fires when espp is consumed via FetchContent/CPM). +# 3.21+ for a reliable PROJECT_IS_TOP_LEVEL (used to default ESPP_INSTALL to ON +# only for a top-level build, so a FetchContent/CPM consumer's `cmake --install` +# never installs espp into the parent's prefix unless it opts in). cmake_minimum_required(VERSION 3.21) +# --------------------------------------------------------------------------- +# Package version, derived from the latest git tag (strip a leading 'v' and keep +# the MAJOR.MINOR.PATCH numeric core). This flows into PROJECT_VERSION and thus +# esppConfigVersion.cmake, so `find_package(espp X.Y.Z)` version checks work. +# Falls back to 0.0.0 when git or a tag is unavailable (e.g. tarball builds) so +# configure still succeeds. +# +# NOTE: the python wheel gets its version independently from setuptools_scm (see +# pyproject.toml [tool.setuptools_scm] / SKBUILD_PROJECT_VERSION); this git-tag +# derivation is only for the CMake / find_package package version. +# --------------------------------------------------------------------------- +set(ESPP_VERSION "0.0.0") +find_package(Git QUIET) +if(GIT_FOUND) + execute_process( + COMMAND ${GIT_EXECUTABLE} describe --tags --abbrev=0 + WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} + OUTPUT_VARIABLE _espp_git_tag + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_QUIET + RESULT_VARIABLE _espp_git_result) + if(_espp_git_result EQUAL 0 AND _espp_git_tag) + string(REGEX REPLACE "^v" "" _espp_git_tag "${_espp_git_tag}") + if(_espp_git_tag MATCHES "^([0-9]+)(\\.[0-9]+)?(\\.[0-9]+)?") + set(ESPP_VERSION "${CMAKE_MATCH_0}") + endif() + endif() +endif() + # Build the PC (host) C++ static library. Python bindings are opt-in (see the # ESPP_BUILD_PYTHON option below); a plain `cmake -S lib` is C++-only. -project(espp VERSION 1.1.8) +project(espp VERSION ${ESPP_VERSION}) +message(STATUS "espp package version: ${PROJECT_VERSION} (from git tag; 0.0.0 = no tag)") # Build the espp Python bindings (_espp)? Default OFF so a plain # `cmake -S lib` produces just the C++ static library + its install/export # (find_package(espp) / espp::espp) with no pybind11 dependency. The standalone # scripts (build.sh / build.ps1) pass -DESPP_BUILD_PYTHON=ON to also build and -# install the python package into lib/pc (this is what CI publishes). Wheel -# builds via scikit-build-core take the SKBUILD path below regardless of this -# option. +# install the python package (the `espp/` package) alongside the C++ package +# under CMAKE_INSTALL_PREFIX (this is what CI publishes). Wheel builds via +# scikit-build-core take the SKBUILD path below regardless of this option. option(ESPP_BUILD_PYTHON "Build the espp Python bindings module (_espp)" OFF) # Install the standard, relocatable CMake package (install(TARGETS ... EXPORT), # esppConfig.cmake, flattened headers) so a separate project can -# `find_package(espp)` and link `espp::espp`. OFF by default so build.sh's plain -# `cmake --build . --target install` (which sets no CMAKE_INSTALL_PREFIX) does -# NOT write the export into the default system prefix (/usr/local). A real -# consumer opts in explicitly: `-DESPP_INSTALL=ON -DCMAKE_INSTALL_PREFIX=`. -option(ESPP_INSTALL "Install espp as a find_package-able package (standard install/export under CMAKE_INSTALL_PREFIX)" OFF) +# `find_package(espp)` and link `espp::espp`. This is THE install path (there is +# no longer a legacy source-tree lib/pc install). Defaults to ON for a top-level +# build (build.sh passes an explicit CMAKE_INSTALL_PREFIX, so no /usr/local +# surprise) and OFF when espp is a subproject, so a FetchContent/CPM consumer's +# `cmake --install` does not drag espp into the parent's prefix unless asked. +option(ESPP_INSTALL "Install espp as a find_package-able package (standard install/export under CMAKE_INSTALL_PREFIX)" ${PROJECT_IS_TOP_LEVEL}) # Report what this configure will actually build (Python is opt-in). if(SKBUILD OR ESPP_BUILD_PYTHON) @@ -55,18 +87,18 @@ include(espp.cmake) include_directories(${ESPP_INCLUDE_DIRS}) -set(LINK_ARG "--whole-archive") - # settings for Windows / MSVC if(MSVC) add_compile_options(/utf-8 /D_USE_MATH_DEFINES /bigobj) add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif() -# settings for MacOS -if(APPLE) - set(LINK_ARG "-all_load") -endif() +# NOTE: whole-archive linking (so global-ctor / registration code such as the +# Windows timer-period adjustment in espp.hpp is not stripped) is now the +# CONSUMER's responsibility, applied where espp::espp is linked (see +# pc/CMakeLists.txt, which wraps it with $). It +# is a no-op on the static archive itself (ar ignores link flags), so it does +# not belong here. if(SKBUILD) # Building a python wheel via scikit-build-core (pip install). Only build the @@ -79,7 +111,8 @@ if(SKBUILD) RUNTIME DESTINATION espp) else() # Standalone build (./build.sh / ./build.ps1): build the C++ static library - # and the python package, and install both into ./pc for local use. + # (and, with -DESPP_BUILD_PYTHON=ON, the python package) and install the + # find_package-able package into CMAKE_INSTALL_PREFIX. set(TARGET_NAME "espp_pc") # main library (which can be built for pc, android, and iOS) @@ -97,7 +130,6 @@ else() # archive name stays libespp_pc.a (OUTPUT_NAME unchanged) for pc/ and CI. set_target_properties(${TARGET_NAME} PROPERTIES EXPORT_NAME espp) set_property(TARGET ${TARGET_NAME} PROPERTY POSITION_INDEPENDENT_CODE ON) - target_link_options(${TARGET_NAME} PRIVATE "${LINK_ARG}") # PUBLIC so the system link deps (pthread, or ws2_32/winmm/iphlpapi on Windows) # propagate to consumers of the exported target. target_link_libraries(${TARGET_NAME} PUBLIC ${ESPP_EXTERNAL_LIBS}) @@ -107,6 +139,10 @@ else() # PUBLIC/INTERFACE so consumers of espp::espp automatically compile as C++23 # (the espp headers require it). target_compile_features(${TARGET_NAME} PUBLIC cxx_std_23) + # PUBLIC so find_package(espp) consumers compile the rtps headers with the SAME + # limits/fragmentation profile the archive was built with (ABI-critical; see + # espp.cmake). These export into esppTargets.cmake as INTERFACE definitions. + target_compile_definitions(${TARGET_NAME} PUBLIC ${ESPP_RTPS_COMPILE_DEFINITIONS}) # --------------------------------------------------------------------------- # Usage requirements (modern, target-based). Expose every include dir espp.cmake @@ -120,32 +156,19 @@ else() target_include_directories(${TARGET_NAME} PUBLIC $) - # --------------------------------------------------------------------------- - # Legacy local install into lib/pc (kept for the pc/ test build and for the - # libespp_* CI artifacts, which consume lib/pc/{include,libespp_pc.a}). This - # writes into the espp SOURCE tree, so guard it to the standalone/CI build - # (build.sh, top-level) and the python-package build -- it must NOT fire when - # espp is a subproject and a parent project runs `cmake --install` (that would - # mutate the espp source tree of a FetchContent/CPM consumer). - # --------------------------------------------------------------------------- - if(PROJECT_IS_TOP_LEVEL OR ESPP_BUILD_PYTHON) - install(TARGETS ${TARGET_NAME} - ARCHIVE DESTINATION ${PROJECT_SOURCE_DIR}/pc) - espp_install_includes(${PROJECT_SOURCE_DIR}/pc) - endif() - # --------------------------------------------------------------------------- # Proper install + export so `find_package(espp)` works from a separate - # project. Installs into GNUInstallDirs under CMAKE_INSTALL_PREFIX. Opt-in via - # -DESPP_INSTALL=ON so the plain build.sh install (no CMAKE_INSTALL_PREFIX) - # does not write to the default system prefix (/usr/local). + # project. Installs into GNUInstallDirs under CMAKE_INSTALL_PREFIX. This is THE + # install path; ESPP_INSTALL defaults ON for a top-level build (build.sh passes + # an explicit prefix) and OFF for a subproject. # --------------------------------------------------------------------------- if(ESPP_INSTALL) espp_install_cmake_package(${TARGET_NAME}) endif() # Build and install the python package (espp/ with the _espp extension inside) + # into CMAKE_INSTALL_PREFIX (put on PYTHONPATH to `import espp`). if(ESPP_BUILD_PYTHON) - espp_install_python_module(${PROJECT_SOURCE_DIR}/pc) + espp_install_python_module() endif() endif() diff --git a/lib/README.md b/lib/README.md index ea57f83e6..c9f649308 100644 --- a/lib/README.md +++ b/lib/README.md @@ -75,26 +75,57 @@ PyPI on each release. ## Building for PC (C++ & Python) -To build the library for use on PC (with C++ and Python), simply build with -cmake: +To build the library for use on PC (with C++ and Python), install it into a +staging prefix with cmake: ``` sh -mkdir build -cd build -cmake .. -cmake --build . --config Release --target install +cmake -S . -B build -DCMAKE_BUILD_TYPE=Release \ + -DESPP_INSTALL=ON -DESPP_BUILD_PYTHON=ON \ + -DCMAKE_INSTALL_PREFIX=../install +cmake --build build --config Release --target install --parallel 4 ``` This is conveniently scripted up for you into [./build.sh](./build.sh) and -[./build.ps1](./build.ps1) scripts you can simply run from your terminal. +[./build.ps1](./build.ps1) scripts you can simply run from your terminal; they +install into `/install`. + +This installs a standard, relocatable CMake package plus the python package +into the prefix: + +* `/lib/libespp_pc.a` - C++ static library. +* `/include/` - all the header files needed to use the library from C++. +* `/lib/cmake/espp/` - `esppConfig.cmake` + friends, so another project + can `find_package(espp)` and link the `espp::espp` target (see below). +* `/espp/` - the `espp` python package (pure-python files, type stubs, + and the compiled `espp._espp` pybind11 extension) - add `` to your + `PYTHONPATH` / `sys.path` to `import espp` from python code. -This will build and install the following files: +The package version is derived from the latest git tag at configure time (a +leading `v` is stripped), and falls back to `0.0.0` for tarball / no-git builds. +The python wheel's version comes separately from `setuptools_scm`. -* `./pc/libespp_pc` - C++ static library for use with other C++ code. -* `./pc/include` - All the header files need for using the library from C++ code. -* `./pc/espp/` - The `espp` python package (pure-python files, type stubs, and - the compiled `espp._espp` pybind11 extension) - add `./pc` to your - `PYTHONPATH` / `sys.path` to `import espp` from python code. +### Using espp from another C++ project (find_package) + +Point `CMAKE_PREFIX_PATH` at the install prefix and link the `espp::espp` +target - it carries the include dirs, the C++23 requirement, and the PUBLIC +system libraries, so nothing else is needed: + +``` cmake +find_package(espp REQUIRED) +target_link_libraries(my_app PRIVATE espp::espp) +# If your app relies on espp's global-ctor / registration code (e.g. the Windows +# timer-period adjustment), whole-archive it (CMake 3.24+): +# target_link_libraries(my_app PRIVATE "$") +``` + +``` sh +cmake -S . -B build -DCMAKE_PREFIX_PATH=/path/to/install +``` + +The same `espp::espp` target is also available without installing, via +`FetchContent` / CPM `add_subdirectory` of `lib/` (build-tree consumers get the +component headers directly). The [../pc](../pc) example tests consume the +installed package this way. ## Updating the python bindings diff --git a/lib/build.ps1 b/lib/build.ps1 index e1e627cbd..b812b37aa 100644 --- a/lib/build.ps1 +++ b/lib/build.ps1 @@ -1,19 +1,19 @@ -# powershell script to build the project using cmake +# powershell script to build espp and install the find_package-able package +# (C++ static library + headers + esppConfig.cmake) together with the python +# `espp` package into a local staging prefix (/install). Point ../pc (and +# any external consumer) at it with -DCMAKE_PREFIX_PATH=/install. -# Create build directory if it doesn't exist -$buildDir = "build" -if (-not (Test-Path -Path $buildDir)) { - New-Item -ItemType Directory -Path $buildDir -} +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$repoRoot = Split-Path -Parent $scriptDir +$prefix = Join-Path $repoRoot "install" +$buildDir = Join-Path $scriptDir "build" -# Change to the build directory -Set-Location -Path $buildDir +# ESPP_BUILD_PYTHON=ON also builds/installs the python package (what CI +# publishes). ESPP_INSTALL=ON installs the find_package package into $prefix. +cmake -S $scriptDir -B $buildDir ` + -DCMAKE_BUILD_TYPE=Release ` + -DESPP_INSTALL=ON ` + -DESPP_BUILD_PYTHON=ON ` + -DCMAKE_INSTALL_PREFIX=$prefix -# Run cmake (ESPP_BUILD_PYTHON=ON also builds/installs the python package) -cmake -DESPP_BUILD_PYTHON=ON .. - -# Run cmake --build . --config Release --target install -cmake --build . --config Release --target install --parallel 4 - -# Change back to the original directory -Set-Location -Path .. +cmake --build $buildDir --config Release --target install --parallel 4 diff --git a/lib/build.sh b/lib/build.sh index 6ae1f3615..433c64814 100755 --- a/lib/build.sh +++ b/lib/build.sh @@ -1,8 +1,20 @@ #!/bin/bash +set -e -mkdir build -cd build -# ESPP_BUILD_PYTHON=ON builds and installs the python package into lib/pc -# alongside the C++ static library + headers (this is what CI publishes). -cmake -DESPP_BUILD_PYTHON=ON .. -cmake --build . --config Release --target install --parallel 4 +# Build espp and install the find_package-able package (C++ static library + +# headers + esppConfig.cmake) together with the python `espp` package into a +# local staging prefix (/install). Point ../pc (and any external consumer) +# at it with -DCMAKE_PREFIX_PATH=/install; put /install on PYTHONPATH +# to `import espp`. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +PREFIX="$REPO_ROOT/install" + +# ESPP_BUILD_PYTHON=ON also builds/installs the python package (what CI +# publishes). ESPP_INSTALL=ON installs the find_package package into PREFIX. +cmake -S "$SCRIPT_DIR" -B "$SCRIPT_DIR/build" \ + -DCMAKE_BUILD_TYPE=Release \ + -DESPP_INSTALL=ON \ + -DESPP_BUILD_PYTHON=ON \ + -DCMAKE_INSTALL_PREFIX="$PREFIX" +cmake --build "$SCRIPT_DIR/build" --config Release --target install --parallel 4 diff --git a/lib/espp.cmake b/lib/espp.cmake index e104f2b42..ebeeb624c 100644 --- a/lib/espp.cmake +++ b/lib/espp.cmake @@ -24,19 +24,20 @@ set(RTPS_LIMITS_PROFILE "${RTPS_LIMITS_PROFILE}" CACHE STRING set_property(CACHE RTPS_LIMITS_PROFILE PROPERTY STRINGS embedded host host_large) if(RTPS_LIMITS_PROFILE STREQUAL "embedded") - add_compile_definitions(RTPS_CONFIG_HEADER="rtps/config_esp32.hpp") + set(RTPS_CONFIG_HEADER_FILE "rtps/config_esp32.hpp") set(RTPS_MAX_SAMPLE_SIZE 262144) # 256 KB (matches config_esp32.hpp) elseif(RTPS_LIMITS_PROFILE STREQUAL "host") - add_compile_definitions(RTPS_CONFIG_HEADER="rtps/config_desktop.hpp") + set(RTPS_CONFIG_HEADER_FILE "rtps/config_desktop.hpp") set(RTPS_MAX_SAMPLE_SIZE 8388608) # 8 MB (matches config_desktop.hpp) elseif(RTPS_LIMITS_PROFILE STREQUAL "host_large") - add_compile_definitions(RTPS_CONFIG_HEADER="rtps/config_host_large.hpp") + set(RTPS_CONFIG_HEADER_FILE "rtps/config_host_large.hpp") set(RTPS_MAX_SAMPLE_SIZE 8388608) # 8 MB (matches config_host_large.hpp) else() message(FATAL_ERROR "Invalid RTPS_LIMITS_PROFILE '${RTPS_LIMITS_PROFILE}' " "(expected: embedded | host | host_large)") endif() +add_compile_definitions(RTPS_CONFIG_HEADER="${RTPS_CONFIG_HEADER_FILE}") message(STATUS "RTPS limits profile: ${RTPS_LIMITS_PROFILE}") # --------------------------------------------------------------------------- @@ -53,6 +54,18 @@ message(STATUS "RTPS limits profile: ${RTPS_LIMITS_PROFILE}") add_compile_definitions(RTPS_ENABLE_FRAGMENTATION RTPS_MAX_SAMPLE_SIZE=${RTPS_MAX_SAMPLE_SIZE}) message(STATUS "RTPS fragmentation: ON (max sample size ${RTPS_MAX_SAMPLE_SIZE} bytes)") +# The RTPS static-limits + fragmentation config is compiled into libespp_pc.a AND +# is part of the public ABI: any consumer that compiles rtps headers (templates / +# inline code) must use the SAME profile, else it sees a different Config and a +# different set of guarded declarations (e.g. addSubMessageDataFrag). Expose them +# as PUBLIC compile definitions on the exported target (see lib/CMakeLists.txt) so +# find_package(espp) consumers inherit them automatically. The above +# add_compile_definitions still covers the in-tree python module / SKBUILD build. +set(ESPP_RTPS_COMPILE_DEFINITIONS + RTPS_CONFIG_HEADER="${RTPS_CONFIG_HEADER_FILE}" + RTPS_ENABLE_FRAGMENTATION + RTPS_MAX_SAMPLE_SIZE=${RTPS_MAX_SAMPLE_SIZE}) + set(ESPP_EXTERNAL_INCLUDES ${ESPP_COMPONENTS}/serialization/detail/alpaca/include ${ESPP_COMPONENTS}/cli/detail/cli/include @@ -184,14 +197,6 @@ set(ESPP_PYTHON_SOURCES ${ESPP_SOURCES} ) -# make an espp_install_includes command that can be used by other scripts, where -# they just need to specify the folder they want to install into -function(espp_install_includes FOLDER) - install(DIRECTORY ${ESPP_INCLUDES} DESTINATION ${FOLDER}/) - install(DIRECTORY ${ESPP_EXTERNAL_INCLUDES} DESTINATION ${FOLDER}/) - install(DIRECTORY ${ESPP_EXTERNAL_INCLUDES_SEPARATE} DESTINATION ${FOLDER}/include/) -endfunction() - # make an espp_install_cmake_package command that gives the C++ library target a # proper install + export so a separate project can `find_package(espp)` and link # `espp::espp`. Installs into GNUInstallDirs under CMAKE_INSTALL_PREFIX: @@ -265,17 +270,18 @@ function(espp_add_python_module) endif() endfunction() -# make an espp_install_python_module command that can be used by other scripts, -# where they just need to specify the folder they want to install into. This -# installs the full `espp` python package (pure-python files + the compiled -# `_espp` extension) into FOLDER/espp so FOLDER can be put on sys.path. -function(espp_install_python_module FOLDER) +# make an espp_install_python_module command that installs the full `espp` python +# package (pure-python files + the compiled `_espp` extension) into the standard +# install prefix as /espp, so CMAKE_INSTALL_PREFIX can be put on sys.path +# / PYTHONPATH to `import espp`. Relative DESTINATIONs are interpreted against +# CMAKE_INSTALL_PREFIX. +function(espp_install_python_module) espp_add_python_module() install(DIRECTORY ${ESPP_PYTHON_BINDINGS_DIR}/espp - DESTINATION ${FOLDER}/ + DESTINATION . PATTERN "__pycache__" EXCLUDE PATTERN ".mypy_cache" EXCLUDE) install(TARGETS _espp - LIBRARY DESTINATION ${FOLDER}/espp/ - RUNTIME DESTINATION ${FOLDER}/espp/) + LIBRARY DESTINATION espp/ + RUNTIME DESTINATION espp/) endfunction() diff --git a/pc/CMakeLists.txt b/pc/CMakeLists.txt index ef907e3d8..1a7fec227 100644 --- a/pc/CMakeLists.txt +++ b/pc/CMakeLists.txt @@ -1,9 +1,22 @@ -cmake_minimum_required (VERSION 3.11) +# 3.24+ for the $ generator expression used +# below (portable whole-archive linking across GNU ld / Apple / MSVC). +cmake_minimum_required (VERSION 3.24) -set(CMAKE_CXX_STANDARD 23) +# Enable CXX at the top level so find_package(espp) -> find_dependency(Threads) +# (FindThreads needs an enabled C/CXX language) resolves. Individual tests still +# declare their own project()/name inside GEN_TESTS below. +project(espp_pc_tests LANGUAGES CXX) -include(${CMAKE_CURRENT_SOURCE_DIR}/../lib/espp.cmake) +set(CMAKE_CXX_STANDARD 23) +# Consume the INSTALLED espp package. Configure with +# -DCMAKE_PREFIX_PATH= +# where is where `cmake --install` put espp (see +# ../lib/build.sh, which installs into /install). The exported espp::espp +# target already carries its include dirs, C++23 requirement, and PUBLIC system +# libraries (pthread, or ws2_32/winmm/iphlpapi on Windows), so nothing else is +# needed here. +find_package(espp REQUIRED) MACRO(GEN_TESTS curdir) # get test files @@ -20,24 +33,17 @@ MACRO(GEN_TESTS curdir) add_compile_options(/utf-8 /D_USE_MATH_DEFINES /bigobj) add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif() - add_executable(${TEST_NAME} ${test_file}) - + add_executable(${TEST_NAME} ${test_file}) - target_include_directories(${TEST_NAME} PRIVATE - ${curdir}/../lib/pc/include) - target_link_directories(${TEST_NAME} PRIVATE - ${curdir}/../lib/pc) + # Link the WHOLE archive so global-ctor / registration code is not stripped + # from the static library -- e.g. the Windows timer-period adjustment in + # espp.hpp, which otherwise gets dropped and caps the timer at ~64 Hz. The + # $ genex expands to the correct per-platform + # flags around espp::espp only (GNU: -Wl,--whole-archive/--no-whole-archive, + # Apple: -force_load, MSVC: /WHOLEARCHIVE), and pulls in the target's PUBLIC + # usage requirements (includes, C++23, system libs) too. target_link_libraries(${TEST_NAME} - PRIVATE espp_pc - PRIVATE ${ESPP_EXTERNAL_LIBS} - ) - # /WHOLEARCHIVE is an MSVC/link.exe flag (gate on MSVC, not WIN32, so - # MinGW/clang Windows builds don't receive it). It ensures the whole archive - # is linked in, otherwise the Windows timer-period adjustment code (from - # espp.hpp) is stripped and the timer runs at a max of ~64 Hz. - if(MSVC) - target_link_options(${TEST_NAME} PRIVATE "/WHOLEARCHIVE:espp_pc.lib") - endif() + PRIVATE "$") ENDFOREACH() ENDMACRO() diff --git a/pc/README.md b/pc/README.md index 5475a8167..d2a46b4a3 100644 --- a/pc/README.md +++ b/pc/README.md @@ -15,9 +15,20 @@ Linux, MacOS, or Windows. ## Setup -First, ensure that you have built the shared objects in the `espp/lib` folder. -If you haven't done so yet, navigate to the `espp/lib` folder and run the -following: +First, install the espp library from the `espp/lib` folder. If you haven't done +so yet, navigate to the `espp/lib` folder and run the following, which installs +espp into `/install`: + +```console +# if macos/linux: +./build.sh +# if windows +./build.ps1 +``` + +Then build the tests here; they `find_package(espp)` from that install prefix +(the [./build.sh](./build.sh) / [./build.ps1](./build.ps1) scripts pass +`-DCMAKE_PREFIX_PATH=/install` for you): ```console # if macos/linux: diff --git a/pc/build.ps1 b/pc/build.ps1 index 08a8ae7cb..42825e09e 100644 --- a/pc/build.ps1 +++ b/pc/build.ps1 @@ -1,19 +1,14 @@ -# powershell script to build the project using cmake +# powershell script to build the pc/ example tests against the INSTALLED espp +# package. Run ../lib/build.ps1 first; it installs espp into /install, +# which we point find_package(espp) at via CMAKE_PREFIX_PATH. -# Create build directory if it doesn't exist -$buildDir = "build" -if (-not (Test-Path -Path $buildDir)) { - New-Item -ItemType Directory -Path $buildDir -} +$scriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path +$repoRoot = Split-Path -Parent $scriptDir +$prefix = Join-Path $repoRoot "install" +$buildDir = Join-Path $scriptDir "build" -# Change to the build directory -Set-Location -Path $buildDir +cmake -S $scriptDir -B $buildDir ` + -DCMAKE_BUILD_TYPE=Release ` + -DCMAKE_PREFIX_PATH=$prefix -# Run cmake -cmake .. - -# Run cmake --build . --config Release -cmake --build . --config Release - -# Change back to the original directory -Set-Location -Path .. +cmake --build $buildDir --config Release --parallel 4 diff --git a/pc/build.sh b/pc/build.sh index 15ab2d0db..ddfd338be 100755 --- a/pc/build.sh +++ b/pc/build.sh @@ -1,6 +1,14 @@ #!/bin/bash +set -e -mkdir build -cd build -cmake .. -cmake --build . --config Release +# Build the pc/ example tests against the INSTALLED espp package. Run +# ../lib/build.sh first; it installs espp into /install, which we point +# find_package(espp) at via CMAKE_PREFIX_PATH. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +PREFIX="$REPO_ROOT/install" + +cmake -S "$SCRIPT_DIR" -B "$SCRIPT_DIR/build" \ + -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_PREFIX_PATH="$PREFIX" +cmake --build "$SCRIPT_DIR/build" --config Release --parallel 4 diff --git a/pyproject.toml b/pyproject.toml index 9c860363d..327535f91 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -47,7 +47,7 @@ sdist.exclude = [ "docs/", "pc/", "python/", - # local build helpers / outputs (lib/pc, build/, _build/ already gitignored) + # local build helpers / outputs (install/, build/, _build/ already gitignored) "*.sh", "*.ps1", # embedded-only submodules and components not referenced by lib/espp.cmake From 7c0105da29d75fd05f606ab524613f511d6ef07d Mon Sep 17 00:00:00 2001 From: William Emfinger Date: Sat, 15 Aug 2026 10:19:24 -0500 Subject: [PATCH 4/4] fix(lib): scope SKBUILD wheel path to espp's own top-level build Address PR #715 review: SKBUILD is configure-wide, so it is also set when a downstream scikit-build-core project pulls espp in via FetchContent. In that case espp is not the top-level project, and the bare if(SKBUILD) branch would build only the _espp wheel module and never create the advertised espp::espp target. Gate the wheel-only path on (SKBUILD AND PROJECT_IS_TOP_LEVEL) via a new ESPP_WHEEL_BUILD variable used by all three SKBUILD-specific checks, so a FetchContent/CPM consumer still gets the C++ library + espp::espp. Co-Authored-By: Claude Opus 4.8 --- lib/CMakeLists.txt | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 60e93af65..34f2ad8fd 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -55,8 +55,20 @@ option(ESPP_BUILD_PYTHON "Build the espp Python bindings module (_espp)" OFF) # `cmake --install` does not drag espp into the parent's prefix unless asked. option(ESPP_INSTALL "Install espp as a find_package-able package (standard install/export under CMAKE_INSTALL_PREFIX)" ${PROJECT_IS_TOP_LEVEL}) +# `SKBUILD` is set for ANY scikit-build-core configure in the tree - including +# when a DOWNSTREAM scikit-build-core project pulls espp in via FetchContent. In +# that case espp is not the top-level project and must still build the normal C++ +# static library + the advertised `espp::espp` target, NOT just the `_espp` wheel +# module. So the wheel-only path is taken only when espp itself is the top-level +# scikit-build project (i.e. `pip wheel .` of espp). +if(SKBUILD AND PROJECT_IS_TOP_LEVEL) + set(ESPP_WHEEL_BUILD ON) +else() + set(ESPP_WHEEL_BUILD OFF) +endif() + # Report what this configure will actually build (Python is opt-in). -if(SKBUILD OR ESPP_BUILD_PYTHON) +if(ESPP_WHEEL_BUILD OR ESPP_BUILD_PYTHON) message(STATUS "Building espp for PC: C++ static library + Python bindings (_espp)") else() message(STATUS "Building espp for PC: C++ static library only (set -DESPP_BUILD_PYTHON=ON for Python bindings)") @@ -66,7 +78,7 @@ endif() # python wheels via pip / scikit-build-core); fall back to fetching it with CPM # for standalone CMake builds (e.g. ./build.sh). Only needed when the python # bindings are actually being built. -if(SKBUILD OR ESPP_BUILD_PYTHON) +if(ESPP_WHEEL_BUILD OR ESPP_BUILD_PYTHON) find_package(pybind11 CONFIG QUIET) if(NOT pybind11_FOUND) include(cmake/CPM.cmake) @@ -100,7 +112,7 @@ endif() # is a no-op on the static archive itself (ar ignores link flags), so it does # not belong here. -if(SKBUILD) +if(ESPP_WHEEL_BUILD) # Building a python wheel via scikit-build-core (pip install). Only build the # espp._espp extension module and install it into the `espp` package; the # pure-python package files come from lib/python_bindings/espp via the