diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 565ac47..ca2871f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -172,6 +172,30 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v7 - - run: cmake -S test/cmake_consumer -B builddir-cmake - - run: cmake --build builddir-cmake - - run: ./builddir-cmake/consumer + - name: add_subdirectory + run: | + cmake -S test/cmake_consumer -B builddir-cmake + cmake --build builddir-cmake + ./builddir-cmake/consumer + - name: install + run: | + cmake -S . -B builddir-install -DCMAKE_INSTALL_PREFIX="$PWD/prefix" + cmake --build builddir-install --target install + # Asking for major version 1 rather than the exact one, so this does not need editing on + # every release. SameMajorVersion is the compatibility the config declares. + - name: find_package + run: | + cmake -S test/cmake_consumer/installed -B builddir-find \ + -DCMAKE_PREFIX_PATH="$PWD/prefix" -DSVECTOR_REQUIRED_VERSION=1 + cmake --build builddir-find + ./builddir-find/consumer-installed + # The happy path passing says nothing about whether the version is actually checked. + - name: a version that is not installed has to be refused + run: | + if cmake -S test/cmake_consumer/installed -B builddir-badver \ + -DCMAKE_PREFIX_PATH="$PWD/prefix" -DSVECTOR_REQUIRED_VERSION=99 >/dev/null 2>&1; then + echo "::error::find_package accepted version 99, so it is not checking versions" + exit 1 + fi + - name: pkg-config + run: PKG_CONFIG_PATH="$PWD/prefix/share/pkgconfig" pkg-config --cflags --modversion svector diff --git a/CMakeLists.txt b/CMakeLists.txt index 1ac9064..a0d64cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,19 +4,80 @@ project("svector" DESCRIPTION " Compact SVO optimized vector for C++17 or higher" HOMEPAGE_URL "https://github.com/martinus/svector") -# determine whether this is a standalone project or included by other projects -set(_svector_is_included ON) +include(GNUInstallDirs) + +# PROJECT_IS_TOP_LEVEL only arrived in CMake 3.21 and the floor here is 3.12, so this is spelled +# by hand. +set(_svector_is_top_level OFF) if(CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR) - set(_svector_is_included OFF) + set(_svector_is_top_level ON) endif() -if(_svector_is_included) - add_library(svector INTERFACE) - add_library(svector::svector ALIAS svector) - target_compile_features(svector INTERFACE cxx_std_17) - target_include_directories( - svector - INTERFACE - $ - $) +# Defined unconditionally. It used to exist only when svector was pulled in with +# add_subdirectory, which meant configuring this repository on its own produced nothing at all -- +# see issue #48, where exactly that looked like the project was broken. An install also needs a +# target to export. +add_library(svector INTERFACE) +add_library(svector::svector ALIAS svector) +target_compile_features(svector INTERFACE cxx_std_17) +target_include_directories( + svector + INTERFACE + $ + $) + +# CMake still never builds or runs the tests, that is meson's job. What it does now is install, +# so that find_package(svector) works and packagers have something to call. +# +# Only when svector is the project being built: a parent that pulled it in with add_subdirectory +# should not find svector's headers turning up in its own install. +if(_svector_is_top_level) + include(CMakePackageConfigHelpers) + + set(_svector_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/svector") + + install(TARGETS svector EXPORT svectorTargets) + install(DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/include/" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}") + install(EXPORT svectorTargets + FILE svectorTargets.cmake + NAMESPACE svector:: + DESTINATION "${_svector_cmakedir}") + + # ARCH_INDEPENDENT stops the version file rejecting a package whose word size differs from + # the consumer's, which for a header only library is not a real incompatibility. It needs + # CMake 3.14, above the floor, so it is added only where it exists. + set(_svector_version_args) + if(NOT CMAKE_VERSION VERSION_LESS 3.14) + list(APPEND _svector_version_args ARCH_INDEPENDENT) + endif() + + # The version comes from project() above, which scripts/lint/lint-version.py keeps in step + # with the ANKERL_SVECTOR_VERSION_* macros, so a package can never claim a version the + # header does not. SameMajorVersion is the compatibility the header's own semver comment + # promises. + write_basic_package_version_file( + "${CMAKE_CURRENT_BINARY_DIR}/svectorConfigVersion.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion + ${_svector_version_args}) + + configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/svectorConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/svectorConfig.cmake" + INSTALL_DESTINATION "${_svector_cmakedir}") + + install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/svectorConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/svectorConfigVersion.cmake" + DESTINATION "${_svector_cmakedir}") + + # For the consumers that never touch CMake at all, which is most of what a distribution + # packages. + configure_file( + "${CMAKE_CURRENT_SOURCE_DIR}/cmake/svector.pc.in" + "${CMAKE_CURRENT_BINARY_DIR}/svector.pc" + @ONLY) + install(FILES "${CMAKE_CURRENT_BINARY_DIR}/svector.pc" + DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig") endif() diff --git a/README.md b/README.md index db6609a..da8f744 100644 --- a/README.md +++ b/README.md @@ -37,6 +37,23 @@ auto v = ankerl::svector(); // 7 int inline, heap only if it outgrows th v.push_back(42); ``` +Copying that one header into your project is a perfectly good way to use it. If you would rather a build system +did it, take your pick: + +```cmake +add_subdirectory(svector) # a submodule, or FetchContent +target_link_libraries(you PRIVATE svector::svector) + +find_package(svector 1 REQUIRED) # after cmake --install, or from a package +target_link_libraries(you PRIVATE svector::svector) +``` + +```meson +svector_dep = dependency('svector') # with subprojects/svector.wrap +``` + +A `pkg-config` file is installed as well, for builds that use neither. + ## How compact can it get? What is the smallest each implementation can be made, how many `uint8_t` fit inline at that size, and what does @@ -290,8 +307,8 @@ A few things are deliberately not the same as `std::vector`: ## Building & Testing -This project uses the [Meson](https://mesonbuild.com/) build system. The `CMakeLists.txt` is a convenience for -consumers and does not build or run the tests. +This project uses the [Meson](https://mesonbuild.com/) build system. The `CMakeLists.txt` is there for consumers +— it defines the target and installs the package — and never builds or runs the tests. ```sh meson setup builddir diff --git a/cmake/svector.pc.in b/cmake/svector.pc.in new file mode 100644 index 0000000..ebceabb --- /dev/null +++ b/cmake/svector.pc.in @@ -0,0 +1,8 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: svector +Description: @PROJECT_DESCRIPTION@ +URL: @PROJECT_HOMEPAGE_URL@ +Version: @PROJECT_VERSION@ +Cflags: -I${includedir} diff --git a/cmake/svectorConfig.cmake.in b/cmake/svectorConfig.cmake.in new file mode 100644 index 0000000..51a2788 --- /dev/null +++ b/cmake/svectorConfig.cmake.in @@ -0,0 +1,5 @@ +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/svectorTargets.cmake") + +check_required_components(svector) diff --git a/meson.build b/meson.build index d45fa91..abc0729 100644 --- a/meson.build +++ b/meson.build @@ -28,5 +28,16 @@ project('svector', 'cpp', ]) incdir = include_directories('include') -subdir('test') + +# So a parent project can use svector as a meson subproject: with this, its dependency('svector') +# resolves to us through the wrap. There was no way to consume this from meson at all before. +svector_dep = declare_dependency(include_directories: incdir) +meson.override_dependency('svector', svector_dep) + +# A parent pulling svector in wants the header, not our test suite and not our headers in its +# install tree. +if not meson.is_subproject() + install_headers('include/ankerl/svector.h', subdir: 'ankerl') + subdir('test') +endif diff --git a/test/cmake_consumer/installed/CMakeLists.txt b/test/cmake_consumer/installed/CMakeLists.txt new file mode 100644 index 0000000..d58456c --- /dev/null +++ b/test/cmake_consumer/installed/CMakeLists.txt @@ -0,0 +1,14 @@ +cmake_minimum_required(VERSION 3.12) +project("svector-find-package-consumer" CXX) + +# Consume svector the way someone does after `cmake --install`, or the way a distribution's +# package expects to be used. Point CMAKE_PREFIX_PATH at the install prefix. +# +# SVECTOR_REQUIRED_VERSION exists so CI can also check that a version that is not there is +# actually refused, rather than only checking the happy path. +set(SVECTOR_REQUIRED_VERSION "" CACHE STRING "version to demand from find_package") + +find_package(svector ${SVECTOR_REQUIRED_VERSION} REQUIRED) + +add_executable(consumer-installed ../main.cpp) +target_link_libraries(consumer-installed PRIVATE svector::svector) diff --git a/test/cmake_consumer/main.cpp b/test/cmake_consumer/main.cpp index 918cfc9..6843340 100644 --- a/test/cmake_consumer/main.cpp +++ b/test/cmake_consumer/main.cpp @@ -2,6 +2,7 @@ #include +// Shared by both consumer projects here, the add_subdirectory one and the find_package one. // Deliberately shallow: this checks that the CMake target carries the include directory and // the C++17 requirement, not that svector behaves. The meson suite owns behaviour. auto main() -> int { @@ -13,7 +14,7 @@ auto main() -> int { return 1; } - std::printf("consumed svector %d.%d.%d through add_subdirectory\n", + std::printf("consumed svector %d.%d.%d through CMake\n", ANKERL_SVECTOR_VERSION_MAJOR, ANKERL_SVECTOR_VERSION_MINOR, ANKERL_SVECTOR_VERSION_PATCH);