diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8ad6a0f..7490c07 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -20,12 +20,38 @@ jobs: - name: Install dependencies run: | if test ${{ matrix.os }} = ubuntu-latest - then sudo apt-get install -y libgtest-dev libmemtailor-dev - else brew install autoconf automake libtool googletest \ + then sudo apt-get install -y pkg-config libgtest-dev libmemtailor-dev + else brew install autoconf automake libtool pkgconf googletest \ Macaulay2/tap/memtailor echo "CPPFLAGS=-I$(brew --prefix)/include" >> $GITHUB_ENV echo "LDFLAGS= -L$(brew --prefix)/lib" >> $GITHUB_ENV fi + - name: Install dependency metadata for macOS CMake consumers + if: matrix.build-system == 'cmake' && runner.os == 'macOS' + shell: bash + run: | + # Current Homebrew CMake bottles omit dependency package metadata. + # Released Autotools builds supply it without depending on another PR. + prefix="$RUNNER_TEMP/dependencies" + export CPPFLAGS="-I$prefix/include $CPPFLAGS" + export LDFLAGS="-L$prefix/lib $LDFLAGS" + export PKG_CONFIG_PATH="$prefix/lib/pkgconfig${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" + for dependency in memtailor:1.4; do + name=${dependency%:*} + version=${dependency#*:} + curl -fsSL "https://github.com/Macaulay2/$name/archive/refs/tags/v$version.tar.gz" \ + -o "$RUNNER_TEMP/$name.tar.gz" + tar -xzf "$RUNNER_TEMP/$name.tar.gz" -C "$RUNNER_TEMP" + ( + cd "$RUNNER_TEMP/$name-$version" + ./autogen.sh + ./configure --prefix="$prefix" --enable-shared + make -j2 + make install + ) + done + echo "CMAKE_PREFIX_PATH=$prefix${CMAKE_PREFIX_PATH:+:$CMAKE_PREFIX_PATH}" >> "$GITHUB_ENV" + echo "PKG_CONFIG_PATH=$PKG_CONFIG_PATH" >> "$GITHUB_ENV" - name: Build and check run: | mkdir build-dir @@ -38,3 +64,37 @@ jobs: ninja src/mathic-unit-tests fi + - name: Test installed CMake packages + if: matrix.build-system == 'cmake' + shell: bash + env: + CMAKE_BUILD_PARALLEL_LEVEL: 2 + run: | + for shared in ON OFF; do + for config in Release Debug; do + build="$RUNNER_TEMP/mathic-$shared-$config" + prefix="$build/install" + cmake -S . -B "$build" -GNinja \ + -DCMAKE_BUILD_TYPE="$config" -DBUILD_SHARED_LIBS="$shared" \ + -DCMAKE_INSTALL_PREFIX="$prefix" + cmake --build "$build" --target install + ctest --test-dir "$build" --output-on-failure + # Check pkg-config before moving its installation prefix. + pc=$(find "$prefix" -name mathic.pc -print -quit) + flags=$(PKG_CONFIG_PATH="$(dirname "$pc")${PKG_CONFIG_PATH:+:$PKG_CONFIG_PATH}" pkg-config --cflags mathic) + expect_debug=OFF + if test "$config" = Debug; then + expect_debug=ON + [[ "$flags" == *-DMATHIC_DEBUG* ]] + else + [[ "$flags" != *-DMATHIC_DEBUG* ]] + fi + mv "$prefix" "$build/relocated" + cmake -S cmake/tests -B "$build/consumer" -GNinja \ + -DCMAKE_BUILD_TYPE=Release -DCMAKE_CXX_STANDARD=11 \ + -DCMAKE_PREFIX_PATH="$build/relocated" \ + -DEXPECT_DEBUG="$expect_debug" + cmake --build "$build/consumer" + ctest --test-dir "$build/consumer" --output-on-failure + done + done diff --git a/CMakeLists.txt b/CMakeLists.txt index 5291a2e..3013be7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,22 +1,22 @@ -cmake_minimum_required(VERSION 3.12) +cmake_minimum_required(VERSION 3.18) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) project(mathic VERSION 1.5 LANGUAGES CXX) +include(GNUInstallDirs) +include(CMakePackageConfigHelpers) +include(CTest) + option(enable_pqsim "Build pqsim" OFF) option(enable_divsim "Build divsim" OFF) set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake" ${CMAKE_MODULE_PATH}) -find_package(Threads 2.1 REQUIRED QUIET) -find_package(Memtailor REQUIRED) - -add_library(memtailor STATIC IMPORTED) -set_target_properties(memtailor PROPERTIES - IMPORTED_LOCATION "${MEMTAILOR_LIBRARIES}" - INTERFACE_INCLUDE_DIRECTORIES "${MEMTAILOR_INCLUDE_DIR}") +find_package(Threads REQUIRED QUIET) +find_package(Memtailor REQUIRED) +add_library(memtailor ALIAS memtailor::memtailor) add_compile_options( -DPACKAGE_NAME="${PROJECT_NAME}" @@ -31,16 +31,34 @@ add_compile_options( add_subdirectory(src) -set_target_properties(mathic PROPERTIES PUBLIC_HEADER src/mathic.h) +set_target_properties(mathic PROPERTIES + VERSION 0.0.5 SOVERSION 0 PUBLIC_HEADER src/mathic.h) +install(TARGETS mathic EXPORT mathicTargets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) +install(DIRECTORY src/mathic DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} + FILES_MATCHING PATTERN "*.h") +install(FILES README.md lgpl-2.0.txt lgpl-2.1.txt lgpl-3.0.txt DESTINATION ${CMAKE_INSTALL_DOCDIR}) -install(TARGETS mathic LIBRARY DESTINATION lib) -install(DIRECTORY src/mathic - DESTINATION include - FILES_MATCHING PATTERN "*.h" - ) -install(FILES README.md lgpl-2.0.txt lgpl-2.1.txt lgpl-3.0.txt - DESTINATION licenses/mathic - ) +set(mathic_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/mathic") +configure_package_config_file(cmake/mathicConfig.cmake.in + "${CMAKE_CURRENT_BINARY_DIR}/mathicConfig.cmake.in" + INSTALL_DESTINATION "${mathic_cmakedir}") +file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/$/mathicConfig.cmake" + INPUT "${CMAKE_CURRENT_BINARY_DIR}/mathicConfig.cmake.in") +write_basic_package_version_file("${CMAKE_CURRENT_BINARY_DIR}/mathicConfigVersion.cmake" + VERSION ${PROJECT_VERSION} COMPATIBILITY SameMajorVersion) +install(FILES cmake/FindMemtailor.cmake DESTINATION "${mathic_cmakedir}") +install(EXPORT mathicTargets NAMESPACE mathic:: DESTINATION "${mathic_cmakedir}") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/$/mathicConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/mathicConfigVersion.cmake" DESTINATION "${mathic_cmakedir}") +configure_file(cmake/mathic.pc.in mathic.pc.in @ONLY) +file(GENERATE OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/$/mathic.pc" + INPUT "${CMAKE_CURRENT_BINARY_DIR}/mathic.pc.in") +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/$/mathic.pc" + DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig) if(enable_pqsim) add_executable(pqsim diff --git a/Makefile.am b/Makefile.am index 6dddbaa..acbfc72 100644 --- a/Makefile.am +++ b/Makefile.am @@ -90,3 +90,7 @@ check: @echo "configured without gtest, so unittests cannot be run." endif + +EXTRA_DIST += cmake/mathicConfig.cmake.in \ + cmake/tests/CMakeLists.txt cmake/tests/consumer.cpp +EXTRA_DIST += cmake/mathic.pc.in diff --git a/README.md b/README.md index 2bf7c95..e8f9bab 100644 --- a/README.md +++ b/README.md @@ -28,3 +28,39 @@ Copyright 2012 2013 Bjarke Hammersholt Roune (http://www.broune.com) and Michael Mathic is licensed for use under the terms of GNU Lesser General Public License version 2.0, and under any later version; the option is yours. See the files lgpl-*.txt in this directory. + +## Building and using the CMake package + +This build requires CMake 3.18 or newer and a C++17 compiler. + +```sh +cmake -S . -B build-dir -GNinja -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=ON -DCMAKE_INSTALL_PREFIX=/your/install/prefix +cmake --build build-dir +ctest --test-dir build-dir --output-on-failure +cmake --install build-dir +``` + +`BUILD_TESTING=OFF` disables the GoogleTest dependency and unit tests. +Both shared and static installations provide headers, pkg-config metadata, +and a relocatable CMake package under the platform's library directory: + +```cmake +find_package(mathic 1.5 CONFIG REQUIRED) +target_link_libraries(my_program PRIVATE mathic::mathic) +``` + +The target supplies C++17, include paths and transitive dependencies. Dependency +CMake configs are preferred; pkg-config metadata is supported for existing +system installations. Older Homebrew CMake bottles may lack both metadata +formats; build the dependency with its released Autotools build, or install a +CMake package that supplies the config. Bare header/library discovery is insufficient to propagate +dependencies' ABI flags. The dependency find modules are shipped with the config. +Use a quoted semicolon-separated `-DCMAKE_PREFIX_PATH='/prefix/one;/prefix/two'` +for nonstandard prefixes; the Unix environment variable uses colons instead. + +The installed package exports `MATHIC_DEBUG` if the library was built in +Debug mode, even when the consumer uses Release. It inherits dependencies' own +ABI flags instead of forcing their Debug layouts. `pkg-config --cflags mathic` +also reports the installed library's Debug flag; pkg-config consumers should +select C++17 or later explicitly. diff --git a/cmake/FindMemtailor.cmake b/cmake/FindMemtailor.cmake index da04bdc..3672613 100644 --- a/cmake/FindMemtailor.cmake +++ b/cmake/FindMemtailor.cmake @@ -1,24 +1,24 @@ -# Try to find the MEMTAILOR libraries -# See https://github.com/Macaulay2/memtailor -# -# This file sets up GLPK for CMake. Once done this will define -# MEMTAILOR_FOUND - system has MEMTAILOR lib -# MEMTAILOR_INCLUDE_DIR - the MEMTAILOR include directory -# MEMTAILOR_LIBRARIES - Libraries needed to use MEMTAILOR -# +# Prefer the upstream config; pkg-config also carries the dependency's ABI flags. # Copyright (c) 2020, Mahrud Sayrafi, -# # Redistribution and use is allowed according to the terms of the BSD license. - -find_path(MEMTAILOR_INCLUDE_DIR NAMES memtailor.h - PATHS ${INCLUDE_INSTALL_DIR} ${CMAKE_INSTALL_PREFIX}/include - PATH_SUFFIXES memtailor - ) -find_library(MEMTAILOR_LIBRARIES NAMES memtailor - PATHS ${LIB_INSTALL_DIR} ${CMAKE_INSTALL_PREFIX}/lib - ) - +if(NOT TARGET memtailor::memtailor) + find_package(memtailor CONFIG QUIET) +endif() +if(NOT TARGET memtailor::memtailor) + find_package(PkgConfig QUIET) + if(PKG_CONFIG_FOUND) + pkg_check_modules(MEMTAILOR QUIET IMPORTED_TARGET memtailor) + if(TARGET PkgConfig::MEMTAILOR) + add_library(memtailor::memtailor INTERFACE IMPORTED) + set_property(TARGET memtailor::memtailor PROPERTY + INTERFACE_LINK_LIBRARIES PkgConfig::MEMTAILOR) + endif() + endif() +endif() +set(MEMTAILOR_FOUND FALSE) +if(TARGET memtailor::memtailor) + set(MEMTAILOR_FOUND TRUE) + set(MEMTAILOR_LIBRARIES memtailor::memtailor) +endif() include(FindPackageHandleStandardArgs) -FIND_PACKAGE_HANDLE_STANDARD_ARGS(Memtailor DEFAULT_MSG MEMTAILOR_INCLUDE_DIR MEMTAILOR_LIBRARIES) - -mark_as_advanced(MEMTAILOR_INCLUDE_DIR MEMTAILOR_LIBRARIES) +find_package_handle_standard_args(Memtailor DEFAULT_MSG MEMTAILOR_FOUND) diff --git a/cmake/mathic.pc.in b/cmake/mathic.pc.in new file mode 100644 index 0000000..a0692d8 --- /dev/null +++ b/cmake/mathic.pc.in @@ -0,0 +1,12 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: mathic +Description: mathic library for Groebner basis computations +URL: https://github.com/Macaulay2/mathic +Version: @PROJECT_VERSION@ +Requires: memtailor +Libs: -L${libdir} -lmathic +Libs.private: @CMAKE_THREAD_LIBS_INIT@ +Cflags: -I${includedir} $<$:-DMATHIC_DEBUG> diff --git a/cmake/mathicConfig.cmake.in b/cmake/mathicConfig.cmake.in new file mode 100644 index 0000000..dee18be --- /dev/null +++ b/cmake/mathicConfig.cmake.in @@ -0,0 +1,14 @@ +@PACKAGE_INIT@ +include(CMakeFindDependencyMacro) +find_dependency(Threads) +set(_mathic_module_path "${CMAKE_MODULE_PATH}") +list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_CURRENT_LIST_DIR}") +find_dependency(Memtailor) +set(CMAKE_MODULE_PATH "${_mathic_module_path}") +include("${CMAKE_CURRENT_LIST_DIR}/mathicTargets.cmake") +# The ABI is determined by the installed library, not the consumer build type. +if($) + set_property(TARGET mathic::mathic APPEND PROPERTY + INTERFACE_COMPILE_DEFINITIONS MATHIC_DEBUG) +endif() +check_required_components(mathic) diff --git a/cmake/tests/CMakeLists.txt b/cmake/tests/CMakeLists.txt new file mode 100644 index 0000000..ba45965 --- /dev/null +++ b/cmake/tests/CMakeLists.txt @@ -0,0 +1,10 @@ +cmake_minimum_required(VERSION 3.18) +project(mathic-consumer LANGUAGES CXX) +find_package(mathic 1.5 CONFIG REQUIRED) +add_executable(consumer consumer.cpp) +target_link_libraries(consumer PRIVATE mathic::mathic) +if(EXPECT_DEBUG) + target_compile_definitions(consumer PRIVATE EXPECT_DEBUG) +endif() +enable_testing() +add_test(NAME installed-consumer COMMAND consumer) diff --git a/cmake/tests/consumer.cpp b/cmake/tests/consumer.cpp new file mode 100644 index 0000000..9b432e0 --- /dev/null +++ b/cmake/tests/consumer.cpp @@ -0,0 +1,14 @@ +#include +#include + +#if defined(EXPECT_DEBUG) != defined(MATHIC_DEBUG) +#error Debug ABI must agree with the installed library +#endif +static_assert(__cplusplus >= 201703L, "C++17 must be inherited"); + +int main() { + memt::Arena arena; + void* p = arena.alloc(32); + arena.freeTop(p); + libmathicIsPresent(); +} diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index f51b537..6b200fa 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,7 +1,3 @@ -if(CMAKE_BUILD_TYPE STREQUAL Debug) - add_compile_options(-DMATHIC_DEBUG -DMEMT_DEBUG) -endif() - add_library(mathic mathic.h mathic.cpp @@ -35,11 +31,16 @@ add_library(mathic mathic/stdinc.h ) +add_library(mathic::mathic ALIAS mathic) +target_compile_features(mathic PUBLIC cxx_std_17) +# Export the installed library's ABI; dependency targets supply their own flags. +target_compile_definitions(mathic PUBLIC $:MATHIC_DEBUG>>) + target_link_libraries(mathic memtailor Threads::Threads) target_include_directories(mathic PUBLIC $ - $ + $ ) include(CTest)