diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 8cd29bc8..3ef0dd28 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -17,7 +17,6 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest] - threading: [TBB] include: - os: ubuntu-latest name: Linux @@ -44,8 +43,8 @@ jobs: uses: actions/cache@v5 with: path: ${{ env.CACHE_PATH }} - key: ${{ runner.os }}-Release-${{ matrix.threading }}-cache-${{ github.sha }} - restore-keys: ${{ runner.os }}-Release-${{ matrix.threading }}-cache + key: ${{ runner.os }}-Release-cache-${{ github.sha }} + restore-keys: ${{ runner.os }}-Release-cache - name: Prepare ccache run: | diff --git a/.github/workflows/optional-dependencies.yml b/.github/workflows/optional-dependencies.yml new file mode 100644 index 00000000..42f6db64 --- /dev/null +++ b/.github/workflows/optional-dependencies.yml @@ -0,0 +1,157 @@ +name: Optional Dependencies + +on: + push: + branches: [main] + pull_request: + +jobs: + forced-missing: + name: Forced missing (${{ matrix.name }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + name: Linux + - os: macos-latest + name: macOS + - os: windows-latest + name: Windows + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 10 + + - name: Configure contract test + run: > + cmake + -S tests/cmake/optional_dependencies + -B build/optional-dependencies-forced-missing + -DPOLYSOLVE_WITH_ACCELERATE=ON + -DPOLYSOLVE_WITH_CHOLMOD=OFF + -DPOLYSOLVE_WITH_UMFPACK=OFF + -DPOLYSOLVE_WITH_SUPERLU=ON + -DPOLYSOLVE_WITH_SPQR=ON + -DPOLYSOLVE_WITH_MKL=OFF + -DPOLYSOLVE_WITH_CUSOLVER=OFF + -DPOLYSOLVE_WITH_PARDISO=ON + -DPOLYSOLVE_WITH_HYPRE=OFF + -DPOLYSOLVE_WITH_AMGCL=OFF + -DPOLYSOLVE_WITH_SPECTRA=OFF + -DCMAKE_DISABLE_FIND_PACKAGE_BLAS=ON + -DCMAKE_DISABLE_FIND_PACKAGE_LAPACK=ON + -DCMAKE_DISABLE_FIND_PACKAGE_SUPERLU=ON + -DCMAKE_DISABLE_FIND_PACKAGE_Pardiso=ON + -DCMAKE_DISABLE_FIND_PACKAGE_SPQR=ON + -DPOLYSOLVE_EXPECT_ACCELERATE=OFF + -DPOLYSOLVE_EXPECT_SUPERLU=OFF + -DPOLYSOLVE_EXPECT_SPQR=OFF + -DPOLYSOLVE_EXPECT_PARDISO=OFF + + all-off: + name: All optional solvers off (${{ matrix.name }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + include: + - os: ubuntu-latest + name: Linux + - os: macos-latest + name: macOS + - os: windows-latest + name: Windows + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 10 + + - name: Configure contract test + run: > + cmake + -S tests/cmake/optional_dependencies + -B build/optional-dependencies-all-off + -DPOLYSOLVE_WITH_ACCELERATE=OFF + -DPOLYSOLVE_WITH_CHOLMOD=OFF + -DPOLYSOLVE_WITH_UMFPACK=OFF + -DPOLYSOLVE_WITH_SUPERLU=OFF + -DPOLYSOLVE_WITH_SPQR=OFF + -DPOLYSOLVE_WITH_MKL=OFF + -DPOLYSOLVE_WITH_CUSOLVER=OFF + -DPOLYSOLVE_WITH_PARDISO=OFF + -DPOLYSOLVE_WITH_HYPRE=OFF + -DPOLYSOLVE_WITH_AMGCL=OFF + -DPOLYSOLVE_WITH_SPECTRA=OFF + + accelerate: + name: Accelerate available (macOS) + runs-on: macos-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 10 + + - name: Configure contract test + run: > + cmake + -S tests/cmake/optional_dependencies + -B build/optional-dependencies-accelerate + -DPOLYSOLVE_WITH_ACCELERATE=ON + -DPOLYSOLVE_WITH_CHOLMOD=OFF + -DPOLYSOLVE_WITH_UMFPACK=OFF + -DPOLYSOLVE_WITH_SUPERLU=OFF + -DPOLYSOLVE_WITH_SPQR=OFF + -DPOLYSOLVE_WITH_MKL=OFF + -DPOLYSOLVE_WITH_CUSOLVER=OFF + -DPOLYSOLVE_WITH_PARDISO=OFF + -DPOLYSOLVE_WITH_HYPRE=OFF + -DPOLYSOLVE_WITH_AMGCL=OFF + -DPOLYSOLVE_WITH_SPECTRA=OFF + -DPOLYSOLVE_EXPECT_ACCELERATE=ON + + - name: Build polysolve_linear + run: cmake --build build/optional-dependencies-accelerate --target polysolve_linear --parallel 2 + + suitesparse: + name: SuiteSparse available (Linux) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v6 + with: + fetch-depth: 10 + + - name: Dependencies + run: | + sudo apt-get update + sudo apt-get -o Acquire::Retries=3 install \ + libblas-dev \ + liblapack-dev \ + libsuitesparse-dev + + - name: Configure contract test + run: > + cmake + -S tests/cmake/optional_dependencies + -B build/optional-dependencies-suitesparse + -DPOLYSOLVE_WITH_ACCELERATE=OFF + -DPOLYSOLVE_WITH_CHOLMOD=ON + -DPOLYSOLVE_WITH_UMFPACK=ON + -DPOLYSOLVE_WITH_SUPERLU=OFF + -DPOLYSOLVE_WITH_SPQR=OFF + -DPOLYSOLVE_WITH_MKL=OFF + -DPOLYSOLVE_WITH_CUSOLVER=OFF + -DPOLYSOLVE_WITH_PARDISO=OFF + -DPOLYSOLVE_WITH_HYPRE=OFF + -DPOLYSOLVE_WITH_AMGCL=OFF + -DPOLYSOLVE_WITH_SPECTRA=OFF + -DPOLYSOLVE_EXPECT_CHOLMOD=ON + -DPOLYSOLVE_EXPECT_UMFPACK=ON + + - name: Build polysolve_linear + run: cmake --build build/optional-dependencies-suitesparse --target polysolve_linear --parallel 2 diff --git a/CMakeLists.txt b/CMakeLists.txt index e1729bd2..f8c1c6b1 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -64,14 +64,24 @@ project(PolySolve DESCRIPTION "Easy-to-use wrapper for linear solver" LANGUAGES CXX) -if(CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "arm64" AND APPLE) - set(POLYSOLVE_NOT_ON_APPLE_SILICON OFF) +set(POLYSOLVE_ON_ARM OFF) +string(TOLOWER "${CMAKE_SYSTEM_PROCESSOR};${CMAKE_HOST_SYSTEM_PROCESSOR};${CMAKE_OSX_ARCHITECTURES}" POLYSOLVE_ARCHITECTURES) +if(POLYSOLVE_ARCHITECTURES MATCHES "(^|[^a-z0-9_])(arm64|aarch64)([^a-z0-9_]|$)") + set(POLYSOLVE_ON_ARM ON) +endif() + +if(APPLE AND POLYSOLVE_ON_ARM) set(POLYSOLVE_ON_APPLE_SILICON ON) else() - set(POLYSOLVE_NOT_ON_APPLE_SILICON ON) set(POLYSOLVE_ON_APPLE_SILICON OFF) endif() +if(POLYSOLVE_ON_ARM) + set(POLYSOLVE_WITH_MKL_DEFAULT OFF) +else() + set(POLYSOLVE_WITH_MKL_DEFAULT ON) +endif() + # Polysolve options option(POLYSOLVE_WITH_SANITIZERS "Enable sanitizers in compilation targets" OFF) option(POLYSOLVE_WITH_UNICODE "Use Unicode in logging messages" ON) @@ -82,7 +92,7 @@ option(POLYSOLVE_WITH_CHOLMOD "Enable Cholmod library" option(POLYSOLVE_WITH_UMFPACK "Enable UmfPack library" ON) option(POLYSOLVE_WITH_SUPERLU "Enable SuperLU library" ON) option(POLYSOLVE_WITH_SPQR "Enable SPQR library" ON) -option(POLYSOLVE_WITH_MKL "Enable MKL library" ${POLYSOLVE_NOT_ON_APPLE_SILICON}) +option(POLYSOLVE_WITH_MKL "Enable MKL library" ${POLYSOLVE_WITH_MKL_DEFAULT}) option(POLYSOLVE_WITH_CUSOLVER "Enable cuSOLVER library" OFF) option(POLYSOLVE_WITH_PARDISO "Enable Pardiso library" OFF) option(POLYSOLVE_WITH_HYPRE "Enable hypre" ON) @@ -133,6 +143,7 @@ list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/find/") # General CMake utils include(polysolve_cpm_cache) include(polysolve_use_colors) +include(polysolve_optional_dependency) # Sort projects inside the solution set_property(GLOBAL PROPERTY USE_FOLDERS ON) @@ -142,7 +153,7 @@ set(CMAKE_POSITION_INDEPENDENT_CODE ON) # Since MKL comes precompiled with /MD on Windows, we need to use the same MSVC runtime library flag # globally for the whole project (otherwise in Debug we will have a mismatch between /MD and /MDd). -if(POLYSOLVE_WITH_MKL) +if(MSVC AND POLYSOLVE_WITH_MKL) # Set MSVC runtime library globally for all targets message(STATUS "Forcing /MD globally due MKL being enabled") set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING "Select the MSVC runtime library") @@ -195,18 +206,26 @@ target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_JSON_SPEC_DIR="${PR # Accelerate solver (Include before Eigen) if(POLYSOLVE_WITH_ACCELERATE) - include(CPM) - CPMAddPackage( - NAME eigen - GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git - GIT_TAG 969c31eefcdfaab11da763bea3f7502086673ab0 - DOWNLOAD_ONLY ON - ) - set(BLA_VENDOR Apple) - find_package(BLAS REQUIRED) - find_package(LAPACK REQUIRED) - target_link_libraries(polysolve_linear PRIVATE BLAS::BLAS LAPACK::LAPACK) - target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_ACCELERATE) + include(accelerate) + if(TARGET polysolve::accelerate) + target_link_libraries(polysolve_linear PRIVATE polysolve::accelerate) + target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_ACCELERATE) + else() + polysolve_note_disabled_dependency("Accelerate" "Target 'polysolve::accelerate' was not created.") + endif() +endif() + +# MKL library (probe before Eigen so Eigen only uses MKL when it is usable) +if(POLYSOLVE_WITH_MKL) + if(POLYSOLVE_ON_ARM) + polysolve_note_disabled_dependency("MKL" "MKL is disabled for ARM targets.") + else() + include(mkl) + endif() +endif() + +if(EIGEN_WITH_MKL AND NOT TARGET mkl::mkl) + set(EIGEN_WITH_MKL OFF CACHE BOOL "Use Eigen with MKL" FORCE) endif() include(eigen) @@ -227,23 +246,30 @@ target_link_libraries(polysolve_linear PUBLIC jse::jse) # Hypre (GNU Lesser General Public License) if(POLYSOLVE_WITH_HYPRE) include(hypre) - target_link_libraries(polysolve_linear PUBLIC HYPRE::HYPRE) - target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_HYPRE) - if(HYPRE_WITH_MPI) - target_compile_definitions(polysolve_linear PUBLIC HYPRE_WITH_MPI) + if(TARGET HYPRE::HYPRE) + target_link_libraries(polysolve_linear PUBLIC HYPRE::HYPRE) + target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_HYPRE) + if(HYPRE_WITH_MPI) + target_compile_definitions(polysolve_linear PUBLIC HYPRE_WITH_MPI) + endif() + else() + polysolve_note_disabled_dependency("HYPRE" "Target 'HYPRE::HYPRE' was not created.") endif() endif() # CHOLMOD solver if(POLYSOLVE_WITH_CHOLMOD) include(suitesparse) - target_link_libraries(polysolve_linear PRIVATE SuiteSparse::CHOLMOD) - target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_CHOLMOD) + if(TARGET SuiteSparse::CHOLMOD) + target_link_libraries(polysolve_linear PRIVATE SuiteSparse::CHOLMOD) + target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_CHOLMOD) + else() + polysolve_note_disabled_dependency("CHOLMOD" "Target 'SuiteSparse::CHOLMOD' was not created.") + endif() endif() -# MKL library -if(POLYSOLVE_WITH_MKL) - include(mkl) +# MKL solver support +if(TARGET mkl::mkl) target_link_libraries(polysolve_linear PRIVATE mkl::mkl) target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_MKL) endif() @@ -255,15 +281,19 @@ if(POLYSOLVE_WITH_PARDISO) target_link_libraries(polysolve_linear PRIVATE Pardiso::Pardiso) target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_PARDISO) else() - message(WARNING "Pardiso not found, solver will not be available.") + polysolve_note_disabled_dependency("Pardiso" "Target 'Pardiso::Pardiso' was not created.") endif() endif() # UmfPack solver if(POLYSOLVE_WITH_UMFPACK) include(suitesparse) - target_link_libraries(polysolve_linear PRIVATE SuiteSparse::UMFPACK) - target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_UMFPACK) + if(TARGET SuiteSparse::UMFPACK) + target_link_libraries(polysolve_linear PRIVATE SuiteSparse::UMFPACK) + target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_UMFPACK) + else() + polysolve_note_disabled_dependency("UMFPACK" "Target 'SuiteSparse::UMFPACK' was not created.") + endif() endif() # SuperLU solver @@ -273,33 +303,41 @@ if(POLYSOLVE_WITH_SUPERLU) target_link_libraries(polysolve_linear PRIVATE SuperLU::SuperLU) target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_SUPERLU) else() - message(WARNING "SuperLU not found, solver will not be available.") + polysolve_note_disabled_dependency("SuperLU" "Target 'SuperLU::SuperLU' was not created.") endif() endif() -# SuperLU solver +# SPQR solver if(POLYSOLVE_WITH_SPQR) include(spqr) if(TARGET SuiteSparse::SPQR) target_link_libraries(polysolve_linear PRIVATE SuiteSparse::SPQR) target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_SPQR) else() - message(WARNING "SPQR Not found, solver will not be available.") + polysolve_note_disabled_dependency("SPQR" "Target 'SuiteSparse::SPQR' was not created.") endif() endif() # AMGCL solver if(POLYSOLVE_WITH_AMGCL) include(amgcl) - target_link_libraries(polysolve_linear PUBLIC amgcl::amgcl) - target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_AMGCL) + if(TARGET amgcl::amgcl) + target_link_libraries(polysolve_linear PUBLIC amgcl::amgcl) + target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_AMGCL) + else() + polysolve_note_disabled_dependency("AMGCL" "Target 'amgcl::amgcl' was not created.") + endif() endif() # Spectra (MPL 2.0) if(POLYSOLVE_WITH_SPECTRA) include(spectra) - target_link_libraries(polysolve_linear PUBLIC Spectra::Spectra) - target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_SPECTRA) + if(TARGET Spectra::Spectra) + target_link_libraries(polysolve_linear PUBLIC Spectra::Spectra) + target_compile_definitions(polysolve_linear PUBLIC POLYSOLVE_WITH_SPECTRA) + else() + polysolve_note_disabled_dependency("Spectra" "Target 'Spectra::Spectra' was not created.") + endif() endif() # cuSolver solvers diff --git a/cmake/find/FindPardiso.cmake b/cmake/find/FindPardiso.cmake index 12992b72..cfdec01a 100644 --- a/cmake/find/FindPardiso.cmake +++ b/cmake/find/FindPardiso.cmake @@ -37,6 +37,6 @@ find_library(PARDISO_LIBRARIES ) include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(PARDISO DEFAULT_MSG PARDISO_LIBRARIES) +find_package_handle_standard_args(Pardiso DEFAULT_MSG PARDISO_LIBRARIES) mark_as_advanced(PARDISO_LIBRARIES) diff --git a/cmake/find/FindSuperLU.cmake b/cmake/find/FindSuperLU.cmake index 9a754d92..ef59c86d 100644 --- a/cmake/find/FindSuperLU.cmake +++ b/cmake/find/FindSuperLU.cmake @@ -22,7 +22,7 @@ find_library(SUPERLU_LIBRARIES superlu PATHS $ENV{SUPERLUDIR} ${LIB_INSTALL_DIR} ################################################################################ # check version specific macros -include(CheckCSourceCompiles) +include(CheckCXXSourceCompiles) include(CMakePushCheckState) cmake_push_check_state() @@ -30,7 +30,7 @@ set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${SUPERLU_INCLUDES}) set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} ${SUPERLU_LIBRARIES}) # check wether version is new enough >= 4.0 -check_c_source_compiles(" +check_cxx_source_compiles(" typedef int int_t; #include #include @@ -42,7 +42,7 @@ int main() }" SUPERLU_MIN_VERSION_4) # check whether version is at least 4.3 -check_c_source_compiles(" +check_cxx_source_compiles(" #include int main(void) { @@ -51,7 +51,7 @@ int main(void) SUPERLU_MIN_VERSION_4_3) # check whether version is at least 5.0 -check_c_source_compiles(" +check_cxx_source_compiles(" typedef int int_t; #include #include diff --git a/cmake/polysolve/polysolve_optional_dependency.cmake b/cmake/polysolve/polysolve_optional_dependency.cmake new file mode 100644 index 00000000..fa0b8e99 --- /dev/null +++ b/cmake/polysolve/polysolve_optional_dependency.cmake @@ -0,0 +1,310 @@ +include_guard(GLOBAL) + +option(POLYSOLVE_FIND_SYSTEM_DEPENDENCIES "Look for package-manager provided dependencies before fetching vendored copies" ON) +option(POLYSOLVE_FETCH_MISSING_DEPENDENCIES "Fetch vendored dependency copies when package-manager provided dependencies are unavailable" ON) + +function(polysolve_note_disabled_dependency name reason) + message(WARNING "${name} requested but unavailable; disabling ${name}. ${reason}") +endfunction() + +function(polysolve_find_system_dependency out_var) + set(options CONFIG MODULE) + set(one_value_args NAME PACKAGE VERSION TARGET SOURCE SOURCE_VAR) + set(multi_value_args COMPONENTS COMPILE_DEFINITIONS LINK_LIBRARIES) + cmake_parse_arguments(POLYSOLVE_FIND + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN} + ) + + if(NOT POLYSOLVE_FIND_NAME) + set(POLYSOLVE_FIND_NAME "${POLYSOLVE_FIND_PACKAGE}") + endif() + + if(NOT POLYSOLVE_FIND_PACKAGE) + message(FATAL_ERROR "polysolve_find_system_dependency requires PACKAGE") + endif() + + if(NOT POLYSOLVE_FIND_TARGET) + message(FATAL_ERROR "polysolve_find_system_dependency requires TARGET") + endif() + + set(${out_var} OFF PARENT_SCOPE) + set(${out_var}_REASON "" PARENT_SCOPE) + + if(NOT POLYSOLVE_FIND_SYSTEM_DEPENDENCIES) + return() + endif() + + set(find_mode) + if(POLYSOLVE_FIND_CONFIG) + set(find_mode CONFIG) + elseif(POLYSOLVE_FIND_MODULE) + set(find_mode MODULE) + endif() + + if(TARGET ${POLYSOLVE_FIND_TARGET}) + if(POLYSOLVE_FIND_SOURCE OR POLYSOLVE_FIND_SOURCE_VAR) + if(POLYSOLVE_FIND_SOURCE_VAR) + polysolve_check_linkable_target(POLYSOLVE_FIND_LINKABLE + NAME ${POLYSOLVE_FIND_NAME} + TARGET ${POLYSOLVE_FIND_TARGET} + SOURCE_VAR ${POLYSOLVE_FIND_SOURCE_VAR} + COMPILE_DEFINITIONS ${POLYSOLVE_FIND_COMPILE_DEFINITIONS} + LINK_LIBRARIES ${POLYSOLVE_FIND_LINK_LIBRARIES} + ) + else() + polysolve_check_linkable_target(POLYSOLVE_FIND_LINKABLE + NAME ${POLYSOLVE_FIND_NAME} + TARGET ${POLYSOLVE_FIND_TARGET} + SOURCE "${POLYSOLVE_FIND_SOURCE}" + COMPILE_DEFINITIONS ${POLYSOLVE_FIND_COMPILE_DEFINITIONS} + LINK_LIBRARIES ${POLYSOLVE_FIND_LINK_LIBRARIES} + ) + endif() + + if(NOT POLYSOLVE_FIND_LINKABLE) + set(${out_var}_REASON "${POLYSOLVE_FIND_LINKABLE_REASON}" PARENT_SCOPE) + return() + endif() + endif() + + message(STATUS "Third-party: using target '${POLYSOLVE_FIND_TARGET}' for ${POLYSOLVE_FIND_NAME}") + set(${out_var} ON PARENT_SCOPE) + return() + endif() + + set(find_args ${POLYSOLVE_FIND_PACKAGE}) + if(POLYSOLVE_FIND_VERSION) + list(APPEND find_args ${POLYSOLVE_FIND_VERSION}) + endif() + if(find_mode) + list(APPEND find_args ${find_mode}) + endif() + list(APPEND find_args QUIET) + if(POLYSOLVE_FIND_COMPONENTS) + list(APPEND find_args COMPONENTS ${POLYSOLVE_FIND_COMPONENTS}) + endif() + + if(POLYSOLVE_FIND_SOURCE OR POLYSOLVE_FIND_SOURCE_VAR) + if(POLYSOLVE_FIND_SOURCE_VAR) + set(check_source_content "${${POLYSOLVE_FIND_SOURCE_VAR}}") + else() + set(check_source_content "${POLYSOLVE_FIND_SOURCE}") + endif() + + string(REGEX REPLACE "[^A-Za-z0-9_]" "_" check_id + "${POLYSOLVE_FIND_NAME}_${POLYSOLVE_FIND_PACKAGE}_${POLYSOLVE_FIND_TARGET}") + set(check_root "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/polysolve_system_dependency_checks") + set(check_source_dir "${check_root}/${check_id}-src") + set(check_binary_dir "${check_root}/${check_id}-build") + set(check_log "${check_root}/${check_id}.log") + set(check_source "${check_source_dir}/main.cpp") + set(check_cmake_file "${check_source_dir}/CMakeLists.txt") + + file(MAKE_DIRECTORY "${check_source_dir}") + file(WRITE "${check_source}" "${check_source_content}") + + set(check_find_package_args ${POLYSOLVE_FIND_PACKAGE}) + if(POLYSOLVE_FIND_VERSION) + list(APPEND check_find_package_args ${POLYSOLVE_FIND_VERSION}) + endif() + if(find_mode) + list(APPEND check_find_package_args ${find_mode}) + endif() + list(APPEND check_find_package_args QUIET) + if(POLYSOLVE_FIND_COMPONENTS) + list(APPEND check_find_package_args COMPONENTS ${POLYSOLVE_FIND_COMPONENTS}) + endif() + + string(REPLACE ";" " " check_find_package_args_line "${check_find_package_args}") + string(REPLACE ";" " " check_compile_definitions_line "${POLYSOLVE_FIND_COMPILE_DEFINITIONS}") + string(REPLACE ";" " " check_link_libraries_line "${POLYSOLVE_FIND_LINK_LIBRARIES}") + + set(check_find_package_hints) + foreach(package_hint_suffix IN ITEMS DIR ROOT) + set(package_hint_var "${POLYSOLVE_FIND_PACKAGE}_${package_hint_suffix}") + if(DEFINED ${package_hint_var}) + string(APPEND check_find_package_hints + "set(${package_hint_var} [==[${${package_hint_var}}]==])\n") + endif() + endforeach() + + set(check_disable_find_package_var "CMAKE_DISABLE_FIND_PACKAGE_${POLYSOLVE_FIND_PACKAGE}") + if(DEFINED ${check_disable_find_package_var}) + string(APPEND check_find_package_hints + "set(${check_disable_find_package_var} [==[${${check_disable_find_package_var}}]==])\n") + endif() + + file(WRITE "${check_cmake_file}" " +cmake_minimum_required(VERSION 3.18) +project(polysolve_system_dependency_check LANGUAGES CXX) + +set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS TRUE CACHE INTERNAL \"\") +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_CXX_EXTENSIONS OFF) +set(CMAKE_POSITION_INDEPENDENT_CODE ${CMAKE_POSITION_INDEPENDENT_CODE}) +set(CMAKE_MODULE_PATH [==[${CMAKE_MODULE_PATH}]==]) +set(CMAKE_PREFIX_PATH [==[${CMAKE_PREFIX_PATH}]==]) +set(CMAKE_FIND_PACKAGE_PREFER_CONFIG [==[${CMAKE_FIND_PACKAGE_PREFER_CONFIG}]==]) +${check_find_package_hints} + +find_package(${check_find_package_args_line}) +if(TARGET ${POLYSOLVE_FIND_TARGET}) + add_executable(polysolve_system_dependency_check main.cpp) + target_compile_definitions(polysolve_system_dependency_check PRIVATE ${check_compile_definitions_line}) + target_link_libraries(polysolve_system_dependency_check PRIVATE ${POLYSOLVE_FIND_TARGET} ${check_link_libraries_line}) +else() + file(WRITE \"\${CMAKE_CURRENT_BINARY_DIR}/missing_target.cpp\" \"#error Target '${POLYSOLVE_FIND_TARGET}' was not created.\\n\") + add_executable(polysolve_system_dependency_check \"\${CMAKE_CURRENT_BINARY_DIR}/missing_target.cpp\") +endif() +") + + set(check_cmake_flags + "-DCMAKE_POSITION_INDEPENDENT_CODE=${CMAKE_POSITION_INDEPENDENT_CODE}" + ) + if(DEFINED CMAKE_OSX_ARCHITECTURES AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL "") + string(REPLACE ";" "\\;" check_osx_architectures "${CMAKE_OSX_ARCHITECTURES}") + list(APPEND check_cmake_flags "-DCMAKE_OSX_ARCHITECTURES=${check_osx_architectures}") + endif() + + if(DEFINED CMAKE_OSX_DEPLOYMENT_TARGET AND NOT CMAKE_OSX_DEPLOYMENT_TARGET STREQUAL "") + list(APPEND check_cmake_flags "-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}") + endif() + + if(DEFINED CMAKE_OSX_SYSROOT AND NOT CMAKE_OSX_SYSROOT STREQUAL "") + list(APPEND check_cmake_flags "-DCMAKE_OSX_SYSROOT=${CMAKE_OSX_SYSROOT}") + endif() + + set(check_result_var "POLYSOLVE_${check_id}_SYSTEM_LINKABLE") + unset(${check_result_var} CACHE) + try_compile(${check_result_var} + "${check_binary_dir}" + "${check_source_dir}" + polysolve_system_dependency_check + polysolve_system_dependency_check + CMAKE_FLAGS ${check_cmake_flags} + OUTPUT_VARIABLE check_output + ) + + if(NOT ${check_result_var}) + file(WRITE "${check_log}" "${check_output}") + set(${out_var}_REASON "A system package compile/link check failed; see ${check_log}." PARENT_SCOPE) + return() + endif() + endif() + + find_package(${find_args}) + + if(TARGET ${POLYSOLVE_FIND_TARGET}) + message(STATUS "Third-party: using system target '${POLYSOLVE_FIND_TARGET}' for ${POLYSOLVE_FIND_NAME}") + set(${out_var} ON PARENT_SCOPE) + else() + set(${out_var}_REASON "Target '${POLYSOLVE_FIND_TARGET}' was not created." PARENT_SCOPE) + endif() +endfunction() + +function(polysolve_should_fetch_dependency out_var name) + if(POLYSOLVE_FETCH_MISSING_DEPENDENCIES) + set(${out_var} ON PARENT_SCOPE) + else() + message(WARNING "${name} was not found and POLYSOLVE_FETCH_MISSING_DEPENDENCIES is OFF.") + set(${out_var} OFF PARENT_SCOPE) + endif() +endfunction() + +function(polysolve_check_linkable_target out_var) + set(options) + set(one_value_args NAME TARGET SOURCE SOURCE_VAR) + set(multi_value_args COMPILE_DEFINITIONS LINK_LIBRARIES) + cmake_parse_arguments(POLYSOLVE_CHECK + "${options}" + "${one_value_args}" + "${multi_value_args}" + ${ARGN} + ) + + if(NOT POLYSOLVE_CHECK_NAME) + message(FATAL_ERROR "polysolve_check_linkable_target requires NAME") + endif() + + if(NOT POLYSOLVE_CHECK_TARGET) + message(FATAL_ERROR "polysolve_check_linkable_target requires TARGET") + endif() + + if(NOT POLYSOLVE_CHECK_SOURCE AND NOT POLYSOLVE_CHECK_SOURCE_VAR) + message(FATAL_ERROR "polysolve_check_linkable_target requires SOURCE or SOURCE_VAR") + endif() + + if(POLYSOLVE_CHECK_SOURCE_VAR) + set(check_source_content "${${POLYSOLVE_CHECK_SOURCE_VAR}}") + else() + set(check_source_content "${POLYSOLVE_CHECK_SOURCE}") + endif() + + if(NOT TARGET ${POLYSOLVE_CHECK_TARGET}) + set(${out_var} OFF PARENT_SCOPE) + set(${out_var}_REASON "Target '${POLYSOLVE_CHECK_TARGET}' was not created." PARENT_SCOPE) + return() + endif() + + foreach(link_dependency IN LISTS POLYSOLVE_CHECK_LINK_LIBRARIES) + if(link_dependency MATCHES "::" AND NOT TARGET ${link_dependency}) + set(${out_var} OFF PARENT_SCOPE) + set(${out_var}_REASON "Target '${link_dependency}' was not created." PARENT_SCOPE) + return() + endif() + endforeach() + + string(REGEX REPLACE "[^A-Za-z0-9_]" "_" check_id + "${POLYSOLVE_CHECK_NAME}_${POLYSOLVE_CHECK_TARGET}") + set(check_root "${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/polysolve_dependency_checks") + set(check_source "${check_root}/${check_id}.cpp") + set(check_binary_dir "${check_root}/${check_id}") + set(check_log "${check_root}/${check_id}.log") + + file(MAKE_DIRECTORY "${check_root}") + file(WRITE "${check_source}" "${check_source_content}") + + set(check_cmake_flags + "-DCMAKE_CXX_STANDARD=17" + "-DCMAKE_CXX_STANDARD_REQUIRED=ON" + "-DCMAKE_CXX_EXTENSIONS=OFF" + "-DCMAKE_POSITION_INDEPENDENT_CODE=${CMAKE_POSITION_INDEPENDENT_CODE}" + ) + + if(DEFINED CMAKE_OSX_ARCHITECTURES AND NOT CMAKE_OSX_ARCHITECTURES STREQUAL "") + string(REPLACE ";" "\\;" check_osx_architectures "${CMAKE_OSX_ARCHITECTURES}") + list(APPEND check_cmake_flags "-DCMAKE_OSX_ARCHITECTURES=${check_osx_architectures}") + endif() + + if(DEFINED CMAKE_OSX_DEPLOYMENT_TARGET AND NOT CMAKE_OSX_DEPLOYMENT_TARGET STREQUAL "") + list(APPEND check_cmake_flags "-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}") + endif() + + if(DEFINED CMAKE_OSX_SYSROOT AND NOT CMAKE_OSX_SYSROOT STREQUAL "") + list(APPEND check_cmake_flags "-DCMAKE_OSX_SYSROOT=${CMAKE_OSX_SYSROOT}") + endif() + + set(check_result_var "POLYSOLVE_${check_id}_LINKABLE") + unset(${check_result_var} CACHE) + try_compile(${check_result_var} + "${check_binary_dir}" + "${check_source}" + CMAKE_FLAGS ${check_cmake_flags} + COMPILE_DEFINITIONS ${POLYSOLVE_CHECK_COMPILE_DEFINITIONS} + LINK_LIBRARIES ${POLYSOLVE_CHECK_TARGET} ${POLYSOLVE_CHECK_LINK_LIBRARIES} + OUTPUT_VARIABLE check_output + ) + + if(${check_result_var}) + set(${out_var} ON PARENT_SCOPE) + set(${out_var}_REASON "" PARENT_SCOPE) + else() + file(WRITE "${check_log}" "${check_output}") + set(${out_var} OFF PARENT_SCOPE) + set(${out_var}_REASON "A compile/link check failed; see ${check_log}." PARENT_SCOPE) + endif() +endfunction() diff --git a/cmake/recipes/LBFGSpp.cmake b/cmake/recipes/LBFGSpp.cmake index 680a3777..349fcc07 100644 --- a/cmake/recipes/LBFGSpp.cmake +++ b/cmake/recipes/LBFGSpp.cmake @@ -1,11 +1,51 @@ # LBFGSpp (https://github.com/yixuan/LBFGSpp) # License: MIT -if(TARGET LBGFSpp::LBGFSpp) +if(TARGET LBFGSpp::LBFGSpp) return() endif() -message(STATUS "Third-party: creating target 'LBGFSpp::LBGFSpp'") +include(polysolve_optional_dependency) + +set(POLYSOLVE_LBFGSPP_CHECK_SOURCE [[ +#include +int main() +{ + LBFGSpp::BFGSMat bfgs; + bfgs.reset(2, 1); + return 0; +} +]]) + +polysolve_find_system_dependency(LBFGSPP_SYSTEM_FOUND + NAME LBFGSpp + PACKAGE lbfgspp + TARGET LBFGSpp::LBFGSpp + CONFIG + SOURCE_VAR POLYSOLVE_LBFGSPP_CHECK_SOURCE +) +if(LBFGSPP_SYSTEM_FOUND) + return() +endif() + +polysolve_find_system_dependency(LBFGSPP_LOWERCASE_SYSTEM_FOUND + NAME LBFGSpp + PACKAGE lbfgspp + TARGET lbfgspp + CONFIG + SOURCE_VAR POLYSOLVE_LBFGSPP_CHECK_SOURCE +) +if(LBFGSPP_LOWERCASE_SYSTEM_FOUND) + add_library(LBFGSpp::LBFGSpp ALIAS lbfgspp) + return() +endif() + +polysolve_should_fetch_dependency(LBFGSPP_SHOULD_FETCH LBFGSpp) +if(NOT LBFGSPP_SHOULD_FETCH) + message(FATAL_ERROR "LBFGSpp is required to build PolySolve.") +endif() + +message(STATUS "Third-party: creating target 'LBFGSpp::LBFGSpp'") include(CPM) CPMAddPackage( diff --git a/cmake/recipes/accelerate.cmake b/cmake/recipes/accelerate.cmake new file mode 100644 index 00000000..b3cf61ad --- /dev/null +++ b/cmake/recipes/accelerate.cmake @@ -0,0 +1,50 @@ +# Apple Accelerate solver support + +include(polysolve_optional_dependency) + +if(TARGET polysolve::accelerate) + return() +endif() + +if(NOT APPLE) + polysolve_note_disabled_dependency("Accelerate" "Accelerate is only supported on macOS.") + return() +endif() + +message(STATUS "Third-party: creating target 'polysolve::accelerate'") + +include(CPM) +CPMAddPackage( + NAME eigen + GIT_REPOSITORY https://gitlab.com/libeigen/eigen.git + GIT_TAG 969c31eefcdfaab11da763bea3f7502086673ab0 + DOWNLOAD_ONLY ON +) + +set(BLA_VENDOR Apple) +find_package(BLAS QUIET) +find_package(LAPACK QUIET) + +set(POLYSOLVE_ACCELERATE_CHECK_SOURCE [[ +#include +int main() +{ + double x[1] = {1.0}; + return cblas_ddot(1, x, 1, x, 1) == 1.0 ? 0 : 1; +} +]]) +polysolve_check_linkable_target(POLYSOLVE_ACCELERATE_LINKABLE + NAME Accelerate + TARGET BLAS::BLAS + LINK_LIBRARIES LAPACK::LAPACK + SOURCE_VAR POLYSOLVE_ACCELERATE_CHECK_SOURCE +) + +if(NOT POLYSOLVE_ACCELERATE_LINKABLE) + polysolve_note_disabled_dependency("Accelerate" "${POLYSOLVE_ACCELERATE_LINKABLE_REASON}") + return() +endif() + +add_library(polysolve_accelerate INTERFACE) +add_library(polysolve::accelerate ALIAS polysolve_accelerate) +target_link_libraries(polysolve_accelerate INTERFACE BLAS::BLAS LAPACK::LAPACK) diff --git a/cmake/recipes/amgcl.cmake b/cmake/recipes/amgcl.cmake index 300f7479..bd131015 100644 --- a/cmake/recipes/amgcl.cmake +++ b/cmake/recipes/amgcl.cmake @@ -14,6 +14,38 @@ if(TARGET amgcl::amgcl) return() endif() +include(polysolve_optional_dependency) + +set(POLYSOLVE_AMGCL_CHECK_SOURCE [[ +#include +#include +#include +#include +int main() +{ + using Backend = amgcl::backend::builtin; + using Solver = amgcl::make_solver, amgcl::runtime::solver::wrapper>; + return sizeof(Solver) > 0 ? 0 : 1; +} +]]) + +polysolve_find_system_dependency(AMGCL_SYSTEM_FOUND + NAME AMGCL + PACKAGE amgcl + TARGET amgcl::amgcl + CONFIG + SOURCE_VAR POLYSOLVE_AMGCL_CHECK_SOURCE +) +if(AMGCL_SYSTEM_FOUND) + return() +endif() + +polysolve_should_fetch_dependency(AMGCL_SHOULD_FETCH AMGCL) +if(NOT AMGCL_SHOULD_FETCH) + polysolve_note_disabled_dependency("AMGCL" "AMGCL was not found and fetching is disabled.") + return() +endif() + message(STATUS "Third-party: creating target 'amgcl::amgcl'") function(amgcl_import_target) diff --git a/cmake/recipes/blas.cmake b/cmake/recipes/blas.cmake index 314fb989..407e4d7e 100644 --- a/cmake/recipes/blas.cmake +++ b/cmake/recipes/blas.cmake @@ -18,12 +18,19 @@ message(STATUS "Third-party: creating target 'BLAS::BLAS'") if("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "arm64" OR "${CMAKE_OSX_ARCHITECTURES}" MATCHES "arm64") # Use Accelerate on macOS M1 set(BLA_VENDOR Apple) - find_package(BLAS REQUIRED) -elseif(POLYSOLVE_WITH_MKL) + find_package(BLAS QUIET) +elseif(POLYSOLVE_WITH_MKL AND TARGET mkl::mkl) # Use MKL if enabled include(mkl) - add_library(BLAS::BLAS ALIAS mkl::mkl) + if(TARGET mkl::mkl) + add_library(BLAS::BLAS INTERFACE IMPORTED GLOBAL) + target_link_libraries(BLAS::BLAS INTERFACE mkl::mkl) + endif() else() # otherwise find system version - find_package(BLAS REQUIRED) + find_package(BLAS QUIET) +endif() + +if(NOT TARGET BLAS::BLAS) + message(WARNING "BLAS not found.") endif() diff --git a/cmake/recipes/boost.cmake b/cmake/recipes/boost.cmake index 07a9154e..20ac28bf 100644 --- a/cmake/recipes/boost.cmake +++ b/cmake/recipes/boost.cmake @@ -13,6 +13,44 @@ if(TARGET Boost::boost) return() endif() +include(polysolve_optional_dependency) + +set(POLYSOLVE_BOOST_CHECK_SOURCE [[ +#include +int main() +{ + return BOOST_VERSION >= 107100 ? 0 : 1; +} +]]) + +polysolve_find_system_dependency(BOOST_SYSTEM_FOUND + NAME Boost + PACKAGE Boost + VERSION 1.71 + TARGET Boost::boost + SOURCE_VAR POLYSOLVE_BOOST_CHECK_SOURCE +) +if(BOOST_SYSTEM_FOUND) + return() +endif() + +polysolve_find_system_dependency(BOOST_HEADERS_SYSTEM_FOUND + NAME Boost + PACKAGE Boost + VERSION 1.71 + TARGET Boost::headers + SOURCE_VAR POLYSOLVE_BOOST_CHECK_SOURCE +) +if(BOOST_HEADERS_SYSTEM_FOUND) + add_library(Boost::boost ALIAS Boost::headers) + return() +endif() + +polysolve_should_fetch_dependency(BOOST_SHOULD_FETCH Boost) +if(NOT BOOST_SHOULD_FETCH) + message(FATAL_ERROR "Boost is required to build PolySolve with AMGCL.") +endif() + message(STATUS "Third-party: creating targets 'Boost::boost'...") set(PREVIOUS_CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS}) diff --git a/cmake/recipes/eigen.cmake b/cmake/recipes/eigen.cmake index 05c1ce04..9c40faf9 100644 --- a/cmake/recipes/eigen.cmake +++ b/cmake/recipes/eigen.cmake @@ -13,10 +13,67 @@ if(TARGET Eigen3::Eigen) return() endif() +include(polysolve_optional_dependency) + option(EIGEN_WITH_MKL "Use Eigen with MKL" OFF) option(EIGEN_DONT_VECTORIZE "Disable Eigen vectorization" OFF) option(EIGEN_MPL2_ONLY "Enable Eigen MPL2 license only" OFF) +set(POLYSOLVE_EIGEN_CHECK_SOURCE [[ +#include +int main() +{ + Eigen::Matrix2d matrix = Eigen::Matrix2d::Identity(); + return matrix.rows() == 2 ? 0 : 1; +} +]]) + +function(eigen_target) + set(target Eigen3::Eigen) + get_target_property(aliased_target ${target} ALIASED_TARGET) + if(aliased_target) + set(target ${aliased_target}) + endif() + + if(EIGEN_MPL2_ONLY) + target_compile_definitions(${target} INTERFACE EIGEN_MPL2_ONLY) + endif() + + if(EIGEN_DONT_VECTORIZE) + target_compile_definitions(${target} INTERFACE EIGEN_DONT_VECTORIZE) + endif() + + if(EIGEN_WITH_MKL) + # TODO: Checks that, on 64bits systems, `mkl::mkl` is using the LP64 interface + # (by looking at the compile definition of the target) + include(mkl) + if(TARGET mkl::mkl) + target_link_libraries(${target} INTERFACE mkl::mkl) + target_compile_definitions(${target} INTERFACE + EIGEN_USE_MKL_ALL + EIGEN_USE_LAPACKE_STRICT + ) + endif() + endif() +endfunction() + +polysolve_find_system_dependency(EIGEN_SYSTEM_FOUND + NAME Eigen + PACKAGE Eigen3 + TARGET Eigen3::Eigen + CONFIG + SOURCE_VAR POLYSOLVE_EIGEN_CHECK_SOURCE +) +if(EIGEN_SYSTEM_FOUND) + eigen_target() + return() +endif() + +polysolve_should_fetch_dependency(EIGEN_SHOULD_FETCH Eigen) +if(NOT EIGEN_SHOULD_FETCH) + message(FATAL_ERROR "Eigen is required to build PolySolve.") +endif() + message(STATUS "Third-party: creating target 'Eigen3::Eigen'") include(CPM) @@ -36,24 +93,7 @@ target_include_directories(Eigen3_Eigen SYSTEM INTERFACE $ ) -if(EIGEN_MPL2_ONLY) - target_compile_definitions(Eigen3_Eigen INTERFACE EIGEN_MPL2_ONLY) -endif() - -if(EIGEN_DONT_VECTORIZE) - target_compile_definitions(Eigen3_Eigen INTERFACE EIGEN_DONT_VECTORIZE) -endif() - -if(EIGEN_WITH_MKL) - # TODO: Checks that, on 64bits systems, `mkl::mkl` is using the LP64 interface - # (by looking at the compile definition of the target) - include(mkl) - target_link_libraries(Eigen3_Eigen INTERFACE mkl::mkl) - target_compile_definitions(Eigen3_Eigen INTERFACE - EIGEN_USE_MKL_ALL - EIGEN_USE_LAPACKE_STRICT - ) -endif() +eigen_target() # On Windows, enable natvis files to improve debugging experience if(WIN32 AND eigen_SOURCE_DIR) diff --git a/cmake/recipes/hypre.cmake b/cmake/recipes/hypre.cmake index 7cfd0c2b..1ebc4d6d 100644 --- a/cmake/recipes/hypre.cmake +++ b/cmake/recipes/hypre.cmake @@ -4,6 +4,37 @@ if(TARGET HYPRE::HYPRE) return() endif() +include(polysolve_optional_dependency) + +set(POLYSOLVE_HYPRE_CHECK_SOURCE [[ +#include +#include +int main() +{ + HYPRE_Solver solver = nullptr; + HYPRE_BoomerAMGCreate(&solver); + HYPRE_BoomerAMGDestroy(solver); + return 0; +} +]]) + +polysolve_find_system_dependency(HYPRE_SYSTEM_FOUND + NAME HYPRE + PACKAGE HYPRE + TARGET HYPRE::HYPRE + CONFIG + SOURCE_VAR POLYSOLVE_HYPRE_CHECK_SOURCE +) +if(HYPRE_SYSTEM_FOUND) + return() +endif() + +polysolve_should_fetch_dependency(HYPRE_SHOULD_FETCH HYPRE) +if(NOT HYPRE_SHOULD_FETCH) + polysolve_note_disabled_dependency("HYPRE" "HYPRE was not found and fetching is disabled.") + return() +endif() + message(STATUS "Third-party: creating target 'HYPRE::HYPRE'") set(HYPRE_WITH_MPI OFF CACHE INTERNAL "" FORCE) diff --git a/cmake/recipes/json.cmake b/cmake/recipes/json.cmake index bd6c6d5a..f068a6a3 100644 --- a/cmake/recipes/json.cmake +++ b/cmake/recipes/json.cmake @@ -13,6 +13,33 @@ if(TARGET nlohmann_json::nlohmann_json) return() endif() +include(polysolve_optional_dependency) + +set(POLYSOLVE_NLOHMANN_JSON_CHECK_SOURCE [[ +#include +int main() +{ + nlohmann::json data = {{"ok", true}}; + return data["ok"].get() ? 0 : 1; +} +]]) + +polysolve_find_system_dependency(NLOHMANN_JSON_SYSTEM_FOUND + NAME nlohmann_json + PACKAGE nlohmann_json + TARGET nlohmann_json::nlohmann_json + CONFIG + SOURCE_VAR POLYSOLVE_NLOHMANN_JSON_CHECK_SOURCE +) +if(NLOHMANN_JSON_SYSTEM_FOUND) + return() +endif() + +polysolve_should_fetch_dependency(NLOHMANN_JSON_SHOULD_FETCH nlohmann_json) +if(NOT NLOHMANN_JSON_SHOULD_FETCH) + message(FATAL_ERROR "nlohmann_json is required to build PolySolve.") +endif() + message(STATUS "Third-party: creating target 'nlohmann_json::nlohmann_json'") # nlohmann_json is a big repo for a single header, so we just download the release archive diff --git a/cmake/recipes/lapack.cmake b/cmake/recipes/lapack.cmake index f559182e..da1a107b 100644 --- a/cmake/recipes/lapack.cmake +++ b/cmake/recipes/lapack.cmake @@ -18,12 +18,19 @@ message(STATUS "Third-party: creating target 'LAPACK::LAPACK'") if("${CMAKE_SYSTEM_PROCESSOR}" MATCHES "arm64" OR "${CMAKE_OSX_ARCHITECTURES}" MATCHES "arm64") # Use Accelerate on macOS M1 set(BLA_VENDOR Apple) - find_package(LAPACK REQUIRED) -elseif(POLYSOLVE_WITH_MKL) + find_package(LAPACK QUIET) +elseif(POLYSOLVE_WITH_MKL AND TARGET mkl::mkl) # Use MKL if enabled include(mkl) - add_library(LAPACK::LAPACK ALIAS mkl::mkl) + if(TARGET mkl::mkl) + add_library(LAPACK::LAPACK INTERFACE IMPORTED GLOBAL) + target_link_libraries(LAPACK::LAPACK INTERFACE mkl::mkl) + endif() else() # otherwise find system version - find_package(LAPACK REQUIRED) + find_package(LAPACK QUIET) +endif() + +if(NOT TARGET LAPACK::LAPACK) + message(WARNING "LAPACK not found.") endif() diff --git a/cmake/recipes/mkl.cmake b/cmake/recipes/mkl.cmake index 7c794579..caeccc5e 100644 --- a/cmake/recipes/mkl.cmake +++ b/cmake/recipes/mkl.cmake @@ -12,6 +12,16 @@ if(TARGET mkl::mkl) return() endif() +if(TARGET mkl_mkl_internal) + return() +endif() + +include(polysolve_optional_dependency) + +if(POLYSOLVE_ON_ARM) + polysolve_note_disabled_dependency("MKL" "MKL is disabled for ARM targets.") + return() +endif() message(STATUS "Third-party: creating target 'mkl::mkl'") @@ -30,7 +40,7 @@ message(STATUS "MKL threading layer: ${MKL_THREADING}") # Linking options set(MKL_LINKING "static" CACHE STRING "Linking strategy to use with MKL (static, dynamic or sdl)") set(MKL_LINKING_CHOICES static dynamic sdl) -set_property(CACHE MKL_LINKING PROPERTY STRINGS ${MKL_LINKINK_CHOICES}) +set_property(CACHE MKL_LINKING PROPERTY STRINGS ${MKL_LINKING_CHOICES}) message(STATUS "MKL linking strategy: ${MKL_LINKING}") # MKL version @@ -75,6 +85,11 @@ elseif(UNIX) set(MKL_PLATFORM linux-64) endif() +if(NOT MKL_PLATFORM) + message(WARNING "MKL is not supported on this platform.") + return() +endif() + if(MKL_VERSION VERSION_EQUAL 2022.2.1) # To compute the md5 checksums for each lib, use the following bash script (replace the target version number): # for f in mkl mkl-include mkl-static mkl-devel; do for os in linux osx win; do cat <(printf "$f-$os-64-md5") <(conda search --override-channel --channel intel $f=2021.3.0 --platform $os-64 -i | grep md5 | cut -d : -f 2); done; done @@ -114,6 +129,13 @@ else() set(MKL_REMOTES mkl-include mkl mkl-devel) endif() +foreach(name IN ITEMS ${MKL_REMOTES}) + if(NOT DEFINED ${name}-${MKL_PLATFORM}-file) + message(WARNING "MKL package '${name}' is not available for ${MKL_PLATFORM}.") + return() + endif() +endforeach() + include(CPM) foreach(name IN ITEMS ${MKL_REMOTES}) CPMAddPackage( @@ -151,11 +173,12 @@ endif() # Note: Since CMake 3.17, find_library accepts a REQUIRED arg that stops processing # if the file is not found, thus removing the need for this assert() function. -function(mkl_assert_is_found var) +macro(mkl_assert_is_found var) if(NOT ${var}) - message(FATAL_ERROR "Could not find " ${var}) + message(WARNING "Could not find ${var}.") + return() endif() -endfunction() +endmacro() # Creates one IMPORTED target for each requested library function(mkl_add_imported_library name) @@ -227,8 +250,8 @@ function(mkl_add_imported_library name) target_link_libraries(mkl::${name} INTERFACE mkl::core) endif() - # Add as dependency to the meta target mkl::mkl - target_link_libraries(mkl::mkl INTERFACE mkl::${name}) + # Add as dependency to the meta target mkl_mkl_internal + target_link_libraries(mkl_mkl_internal INTERFACE mkl::${name}) endfunction() # On Windows, creates an IMPORTED target for each given .dll @@ -256,7 +279,7 @@ function(mkl_add_shared_libraries) IMPORTED_LOCATION "${${DLLVAR}}" ) - # Set as dependency to the meta target mkl::mkl. We cannot directly use `target_link_libraries`, since a MODULE + # Set as dependency to the meta target mkl_mkl_internal. We cannot directly use `target_link_libraries`, since a MODULE # target represents a runtime dependency and cannot be linked against. Instead, we populate a custom property # mkl_RUNTIME_DEPENDENCIES, which we use to manually copy dlls into an executable folder. # @@ -265,7 +288,7 @@ function(mkl_add_shared_libraries) # # See also this thread for explicit tracking of runtime requirements: # https://gitlab.kitware.com/cmake/cmake/-/issues/20417 - set_property(TARGET mkl::mkl PROPERTY mkl_RUNTIME_DEPENDENCIES mkl::${name} APPEND) + set_property(TARGET mkl_mkl_internal PROPERTY mkl_RUNTIME_DEPENDENCIES mkl::${name} APPEND) endforeach() endfunction() @@ -298,8 +321,9 @@ endfunction() ################################################################################ -# Create a proper imported target for MKL -add_library(mkl::mkl INTERFACE IMPORTED GLOBAL) +# Create the internal target first. The public mkl::mkl alias is only exposed +# after a compile/link probe succeeds. +add_library(mkl_mkl_internal INTERFACE IMPORTED GLOBAL) # Find header directory find_path(MKL_INCLUDE_DIR @@ -311,7 +335,7 @@ find_path(MKL_INCLUDE_DIR ) mkl_assert_is_found(MKL_INCLUDE_DIR) message(STATUS "MKL include dir: ${MKL_INCLUDE_DIR}") -target_include_directories(mkl::mkl INTERFACE ${MKL_INCLUDE_DIR}) +target_include_directories(mkl_mkl_internal INTERFACE ${MKL_INCLUDE_DIR}) if(MKL_LINKING STREQUAL sdl) # Link against a single dynamic lib @@ -370,18 +394,51 @@ endif() # Compile definitions. if(MKL_INTERFACE STREQUAL "ilp64") - target_compile_definitions(mkl::mkl INTERFACE MKL_ILP64) + target_compile_definitions(mkl_mkl_internal INTERFACE MKL_ILP64) endif() # Also requires the math system library (-lm)? if(NOT MSVC) find_library(LIBM_LIBRARY m) mkl_assert_is_found(LIBM_LIBRARY) - target_link_libraries(mkl::mkl INTERFACE ${LIBM_LIBRARY}) + target_link_libraries(mkl_mkl_internal INTERFACE ${LIBM_LIBRARY}) endif() # If using TBB, we need to specify the dependency +set(POLYSOLVE_MKL_LINK_CHECK ON) +set(POLYSOLVE_MKL_CHECK_LINK_LIBRARIES) if(MKL_THREADING STREQUAL "tbb") include(onetbb) target_link_libraries(mkl::tbb_thread INTERFACE TBB::tbb) + # When oneTBB is fetched from source, TBB::tbb is a build-tree target. CMake's + # source-file try_compile cannot carry that target through MKL's imported + # target graph, so the real build validates this path. + set(POLYSOLVE_MKL_LINK_CHECK OFF) +elseif(MKL_THREADING STREQUAL "openmp") + list(APPEND POLYSOLVE_MKL_CHECK_LINK_LIBRARIES OpenMP::OpenMP_CXX) endif() + +set(POLYSOLVE_MKL_CHECK_SOURCE [[ +#include +int main() +{ + MKLVersion version; + mkl_get_version(&version); + return 0; +} +]]) +if(POLYSOLVE_MKL_LINK_CHECK) + polysolve_check_linkable_target(POLYSOLVE_MKL_LINKABLE + NAME MKL + TARGET mkl_mkl_internal + SOURCE_VAR POLYSOLVE_MKL_CHECK_SOURCE + LINK_LIBRARIES ${POLYSOLVE_MKL_CHECK_LINK_LIBRARIES} + ) + + if(NOT POLYSOLVE_MKL_LINKABLE) + polysolve_note_disabled_dependency("MKL" "${POLYSOLVE_MKL_LINKABLE_REASON}") + return() + endif() +endif() + +add_library(mkl::mkl ALIAS mkl_mkl_internal) diff --git a/cmake/recipes/onetbb.cmake b/cmake/recipes/onetbb.cmake index f37bb92b..51a280e7 100644 --- a/cmake/recipes/onetbb.cmake +++ b/cmake/recipes/onetbb.cmake @@ -13,6 +13,33 @@ if(TARGET TBB::tbb) return() endif() +include(polysolve_optional_dependency) + +set(POLYSOLVE_TBB_CHECK_SOURCE [[ +#include +int main() +{ + oneapi::tbb::task_arena arena; + return arena.max_concurrency() > 0 ? 0 : 1; +} +]]) + +polysolve_find_system_dependency(TBB_SYSTEM_FOUND + NAME oneTBB + PACKAGE TBB + TARGET TBB::tbb + CONFIG + SOURCE_VAR POLYSOLVE_TBB_CHECK_SOURCE +) +if(TBB_SYSTEM_FOUND) + return() +endif() + +polysolve_should_fetch_dependency(TBB_SHOULD_FETCH oneTBB) +if(NOT TBB_SHOULD_FETCH) + message(FATAL_ERROR "oneTBB is required for MKL TBB threading.") +endif() + message(STATUS "Third-party: creating target 'TBB::tbb' (OneTBB)") # Emscripten sets CMAKE_SYSTEM_PROCESSOR to "x86". Change it to "WASM" to prevent TBB from diff --git a/cmake/recipes/pardiso.cmake b/cmake/recipes/pardiso.cmake index e0e7eb48..dc3bb961 100644 --- a/cmake/recipes/pardiso.cmake +++ b/cmake/recipes/pardiso.cmake @@ -1,15 +1,25 @@ # Pardiso solver +include(polysolve_optional_dependency) + if(TARGET Pardiso::Pardiso) return() endif() +if(TARGET Pardiso_Pardiso) + return() +endif() message(STATUS "Third-party: creating targets 'Pardiso::Pardiso'") # We do not have a build recipe for this, so find it as a system installed library. -find_package(Pardiso) +find_package(Pardiso QUIET) -if(PARDISO_FOUND) +if(Pardiso_FOUND OR PARDISO_FOUND) + find_package(LAPACK QUIET) + if(NOT LAPACK_FOUND) + message(WARNING "Pardiso found but LAPACK was not found.") + return() + endif() # find_package(OpenMP) # if( OpenMP_CXX_FOUND ) @@ -19,15 +29,27 @@ if(PARDISO_FOUND) # message(FATAL_ERROR "unable to find omp") # endif() - add_library(Pardiso_Pardiso INTERFACE) - add_library(Pardiso::Pardiso ALIAS Pardiso_Pardiso) - - target_link_libraries(Pardiso_Pardiso INTERFACE ${PARDISO_LIBRARIES}) - - find_package(LAPACK) - if(LAPACK_FOUND) - target_link_libraries(Pardiso_Pardiso INTERFACE ${LAPACK_LIBRARIES}) - else() - message(FATAL_ERROR "unable to find lapack") + add_library(Pardiso_Pardiso INTERFACE IMPORTED GLOBAL) + target_link_libraries(Pardiso_Pardiso INTERFACE ${PARDISO_LIBRARIES} ${LAPACK_LIBRARIES}) + + set(POLYSOLVE_PARDISO_CHECK_SOURCE [[ +extern "C" void pardisoinit(void *, int *, int *, int *, double *, int *); +int main() +{ + auto *symbol = &pardisoinit; + return symbol == nullptr ? 1 : 0; +} +]]) + polysolve_check_linkable_target(POLYSOLVE_PARDISO_LINKABLE + NAME Pardiso + TARGET Pardiso_Pardiso + SOURCE_VAR POLYSOLVE_PARDISO_CHECK_SOURCE + ) + + if(NOT POLYSOLVE_PARDISO_LINKABLE) + polysolve_note_disabled_dependency("Pardiso" "${POLYSOLVE_PARDISO_LINKABLE_REASON}") + return() endif() + + add_library(Pardiso::Pardiso ALIAS Pardiso_Pardiso) endif() diff --git a/cmake/recipes/spdlog.cmake b/cmake/recipes/spdlog.cmake index a61cd2a7..ebcb1981 100644 --- a/cmake/recipes/spdlog.cmake +++ b/cmake/recipes/spdlog.cmake @@ -17,6 +17,33 @@ if(TARGET spdlog::spdlog) return() endif() +include(polysolve_optional_dependency) + +set(POLYSOLVE_SPDLOG_CHECK_SOURCE [[ +#include +int main() +{ + spdlog::info("polysolve dependency check"); + return 0; +} +]]) + +polysolve_find_system_dependency(SPDLOG_SYSTEM_FOUND + NAME spdlog + PACKAGE spdlog + TARGET spdlog::spdlog + CONFIG + SOURCE_VAR POLYSOLVE_SPDLOG_CHECK_SOURCE +) +if(SPDLOG_SYSTEM_FOUND) + return() +endif() + +polysolve_should_fetch_dependency(SPDLOG_SHOULD_FETCH spdlog) +if(NOT SPDLOG_SHOULD_FETCH) + message(FATAL_ERROR "spdlog is required to build PolySolve.") +endif() + message(STATUS "Third-party: creating target 'spdlog::spdlog'") option(SPDLOG_INSTALL "Generate the install target" ON) @@ -34,4 +61,4 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang" OR target_compile_options(spdlog PRIVATE "-Wno-sign-conversion" ) -endif() \ No newline at end of file +endif() diff --git a/cmake/recipes/spectra.cmake b/cmake/recipes/spectra.cmake index de2b83af..d9f747a5 100644 --- a/cmake/recipes/spectra.cmake +++ b/cmake/recipes/spectra.cmake @@ -4,6 +4,38 @@ if(TARGET Spectra::Spectra) return() endif() +include(polysolve_optional_dependency) + +set(POLYSOLVE_SPECTRA_CHECK_SOURCE [[ +#include +#include +#include +int main() +{ + Eigen::SparseMatrix matrix(2, 2); + matrix.setIdentity(); + Spectra::SparseSymMatProd op(matrix); + return op.rows() == 2 ? 0 : 1; +} +]]) + +polysolve_find_system_dependency(SPECTRA_SYSTEM_FOUND + NAME Spectra + PACKAGE Spectra + TARGET Spectra::Spectra + CONFIG + SOURCE_VAR POLYSOLVE_SPECTRA_CHECK_SOURCE +) +if(SPECTRA_SYSTEM_FOUND) + return() +endif() + +polysolve_should_fetch_dependency(SPECTRA_SHOULD_FETCH Spectra) +if(NOT SPECTRA_SHOULD_FETCH) + polysolve_note_disabled_dependency("Spectra" "Spectra was not found and fetching is disabled.") + return() +endif() + message(STATUS "Third-party: creating target 'Spectra::Spectra'") include(CPM) diff --git a/cmake/recipes/spqr.cmake b/cmake/recipes/spqr.cmake index 05cb74de..47a94813 100644 --- a/cmake/recipes/spqr.cmake +++ b/cmake/recipes/spqr.cmake @@ -1,11 +1,46 @@ # SPQR solver -if(TARGET SparseSuite::SPQR) +if(TARGET SuiteSparse::SPQR) return() endif() +include(polysolve_optional_dependency) + +set(POLYSOLVE_SPQR_CHECK_SOURCE [[ +#include +int main() +{ + cholmod_common common; + cholmod_l_start(&common); + SuiteSparseQR_factorization *qr = nullptr; + SuiteSparseQR_free(&qr, &common); + cholmod_l_finish(&common); + return 0; +} +]]) + message(STATUS "Third-party: creating targets 'SuiteSparse::SPQR'") # We do not have a build recipe for this, so find it as a system installed library. -find_package(SPQR) +polysolve_find_system_dependency(SPQR_SUITESPARSE_SYSTEM_FOUND + NAME SPQR + PACKAGE SuiteSparse + TARGET SuiteSparse::SPQR + CONFIG + SOURCE_VAR POLYSOLVE_SPQR_CHECK_SOURCE +) +if(SPQR_SUITESPARSE_SYSTEM_FOUND) + return() +endif() + +polysolve_find_system_dependency(SPQR_SYSTEM_FOUND + NAME SPQR + PACKAGE SPQR + TARGET SPQR::SPQR + SOURCE_VAR POLYSOLVE_SPQR_CHECK_SOURCE +) +if(SPQR_SYSTEM_FOUND AND NOT TARGET SuiteSparse::SPQR) + add_library(SuiteSparse::SPQR INTERFACE IMPORTED GLOBAL) + target_link_libraries(SuiteSparse::SPQR INTERFACE SPQR::SPQR) +endif() diff --git a/cmake/recipes/suitesparse.cmake b/cmake/recipes/suitesparse.cmake index da6b0430..355c79ff 100644 --- a/cmake/recipes/suitesparse.cmake +++ b/cmake/recipes/suitesparse.cmake @@ -14,7 +14,88 @@ # compared to simplicial mode. This is optional and can be disabled by setting WITH_LICENSE to LGPL. #--------------------------------------------------------------------------------------------------- -if(TARGET SuiteSparse::CHOLMOD) +if(TARGET SuiteSparse::CHOLMOD AND TARGET SuiteSparse::UMFPACK) + return() +endif() + +include(polysolve_optional_dependency) + +set(POLYSOLVE_CHOLMOD_CHECK_SOURCE [[ +#include +int main() +{ + cholmod_common common; + cholmod_start(&common); + cholmod_finish(&common); + return 0; +} +]]) + +set(POLYSOLVE_UMFPACK_CHECK_SOURCE [[ +#include +int main() +{ + double control[UMFPACK_CONTROL]; + umfpack_di_defaults(control); + return 0; +} +]]) + +set(POLYSOLVE_SUITESPARSE_SPQR_CHECK_SOURCE [[ +#include +int main() +{ + cholmod_common common; + cholmod_l_start(&common); + SuiteSparseQR_factorization *qr = nullptr; + SuiteSparseQR_free(&qr, &common); + cholmod_l_finish(&common); + return 0; +} +]]) + +set(SUITESPARSE_SYSTEM_FOUND OFF) + +polysolve_find_system_dependency(SUITESPARSE_CHOLMOD_SYSTEM_FOUND + NAME SuiteSparse + PACKAGE SuiteSparse + TARGET SuiteSparse::CHOLMOD + CONFIG + SOURCE_VAR POLYSOLVE_CHOLMOD_CHECK_SOURCE +) +if(SUITESPARSE_CHOLMOD_SYSTEM_FOUND) + set(SUITESPARSE_SYSTEM_FOUND ON) +endif() + +polysolve_find_system_dependency(SUITESPARSE_UMFPACK_SYSTEM_FOUND + NAME SuiteSparse + PACKAGE SuiteSparse + TARGET SuiteSparse::UMFPACK + CONFIG + SOURCE_VAR POLYSOLVE_UMFPACK_CHECK_SOURCE +) +if(SUITESPARSE_UMFPACK_SYSTEM_FOUND) + set(SUITESPARSE_SYSTEM_FOUND ON) +endif() + +polysolve_find_system_dependency(SUITESPARSE_SPQR_SYSTEM_FOUND + NAME SuiteSparse + PACKAGE SuiteSparse + TARGET SuiteSparse::SPQR + CONFIG + SOURCE_VAR POLYSOLVE_SUITESPARSE_SPQR_CHECK_SOURCE +) +if(SUITESPARSE_SPQR_SYSTEM_FOUND) + set(SUITESPARSE_SYSTEM_FOUND ON) +endif() + +if(SUITESPARSE_SYSTEM_FOUND) + return() +endif() + +polysolve_should_fetch_dependency(SUITESPARSE_SHOULD_FETCH SuiteSparse) +if(NOT SUITESPARSE_SHOULD_FETCH) + message(WARNING "SuiteSparse was not found and fetching is disabled; SuiteSparse solvers will not be available.") return() endif() @@ -45,7 +126,8 @@ include(blas) include(lapack) if(NOT TARGET BLAS::BLAS OR NOT TARGET LAPACK::LAPACK) - message(FATAL_ERROR "BLAS/LAPACK configuration error") + message(WARNING "SuiteSparse requires BLAS and LAPACK; SuiteSparse solvers will not be available.") + return() endif() function(suitesparse_import_target) @@ -115,7 +197,8 @@ suitesparse_import_target() foreach(name IN ITEMS cholmod SuiteSparse::CHOLMOD) if(NOT TARGET ${name}) - message(FATAL_ERROR "SuiteSparse error: missing '${name}' target.") + message(WARNING "SuiteSparse error: missing '${name}' target.") + return() endif() endforeach() @@ -123,7 +206,8 @@ endforeach() if(WITH_LICENSE STREQUAL LGPL) get_target_property(target_type cholmod TYPE) if(NOT ${target_type} STREQUAL "SHARED_LIBRARY") - message(FATAL_ERROR "SuiteSparse error: 'cholmod' should be SHARED_LIBRARY, but is ${target_type}.") + message(WARNING "SuiteSparse error: 'cholmod' should be SHARED_LIBRARY, but is ${target_type}.") + return() endif() endif() diff --git a/cmake/recipes/superlu.cmake b/cmake/recipes/superlu.cmake index c0349866..e39c6365 100644 --- a/cmake/recipes/superlu.cmake +++ b/cmake/recipes/superlu.cmake @@ -1,18 +1,45 @@ # SuperLU solver +include(polysolve_optional_dependency) + if(TARGET SuperLU::SuperLU) return() endif() +if(TARGET SuperLU_SuperLU) + return() +endif() message(STATUS "Third-party: creating targets 'SuperLU::SuperLU'") # We do not have a build recipe for this, so find it as a system installed library. -find_package(SUPERLU) +find_package(SUPERLU QUIET) if(SUPERLU_FOUND) - add_library(SuperLU_SuperLU INTERFACE) - add_library(SuperLU::SuperLU ALIAS SuperLU_SuperLU) + add_library(SuperLU_SuperLU INTERFACE IMPORTED GLOBAL) target_include_directories(SuperLU_SuperLU INTERFACE ${SUPERLU_INCLUDES}) target_link_libraries(SuperLU_SuperLU INTERFACE ${SUPERLU_LIBRARIES}) target_compile_definitions(SuperLU_SuperLU INTERFACE ${SUPERLU_DEFINES}) + + set(POLYSOLVE_SUPERLU_CHECK_SOURCE [[ +#include +int main() +{ + SuperLUStat_t stat; + StatInit(&stat); + StatFree(&stat); + return 0; +} +]]) + polysolve_check_linkable_target(POLYSOLVE_SUPERLU_LINKABLE + NAME SuperLU + TARGET SuperLU_SuperLU + SOURCE_VAR POLYSOLVE_SUPERLU_CHECK_SOURCE + ) + + if(NOT POLYSOLVE_SUPERLU_LINKABLE) + polysolve_note_disabled_dependency("SuperLU" "${POLYSOLVE_SUPERLU_LINKABLE_REASON}") + return() + endif() + + add_library(SuperLU::SuperLU ALIAS SuperLU_SuperLU) endif() diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 8f7b56ca..6e7f4f7e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,30 +8,30 @@ set(test_sources test_nonlinear_solver.cpp test_json.cpp ) -add_executable(unit_tests ${test_sources}) +add_executable(polysolve_unit_tests ${test_sources}) ################################################################################ # Required Libraries ################################################################################ include(catch2) -target_link_libraries(unit_tests PUBLIC Catch2::Catch2) +target_link_libraries(polysolve_unit_tests PUBLIC Catch2::Catch2) -target_link_libraries(unit_tests PUBLIC polysolve::polysolve) +target_link_libraries(polysolve_unit_tests PUBLIC polysolve::polysolve) include(polysolve_warnings) -target_link_libraries(unit_tests PRIVATE polysolve::warnings) +target_link_libraries(polysolve_unit_tests PRIVATE polysolve::warnings) if(POLYSOLVE_WITH_SANITIZERS) - add_sanitizers(unit_tests) + add_sanitizers(polysolve_unit_tests) endif() if(POLYSOLVE_WITH_AMGCL) - target_compile_definitions(unit_tests PRIVATE -DPOLYSOLVE_WITH_AMGCL) + target_compile_definitions(polysolve_unit_tests PRIVATE -DPOLYSOLVE_WITH_AMGCL) endif() include(polyfem-data) -target_link_libraries(unit_tests PRIVATE polyfem::data) +target_link_libraries(polysolve_unit_tests PRIVATE polyfem::data) ################################################################################ # Register tests @@ -43,4 +43,4 @@ endforeach() # Register tests set(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS ON) -catch_discover_tests(unit_tests) +catch_discover_tests(polysolve_unit_tests) diff --git a/tests/cmake/optional_dependencies/CMakeLists.txt b/tests/cmake/optional_dependencies/CMakeLists.txt new file mode 100644 index 00000000..b5c78148 --- /dev/null +++ b/tests/cmake/optional_dependencies/CMakeLists.txt @@ -0,0 +1,84 @@ +cmake_minimum_required(VERSION 3.18) + +project(PolySolveOptionalDependencyContract LANGUAGES CXX) + +set(POLYSOLVE_SOURCE_DIR "${CMAKE_CURRENT_LIST_DIR}/../../.." CACHE PATH "Path to the PolySolve source tree") +get_filename_component(POLYSOLVE_SOURCE_DIR "${POLYSOLVE_SOURCE_DIR}" ABSOLUTE) + +set(POLYSOLVE_WITH_TESTS OFF CACHE BOOL "" FORCE) + +add_subdirectory("${POLYSOLVE_SOURCE_DIR}" polysolve) + +function(polysolve_collect_linear_definitions out_var) + set(definitions) + foreach(property IN ITEMS COMPILE_DEFINITIONS INTERFACE_COMPILE_DEFINITIONS) + get_target_property(values polysolve_linear ${property}) + if(values) + list(APPEND definitions ${values}) + endif() + endforeach() + list(REMOVE_DUPLICATES definitions) + set(${out_var} ${definitions} PARENT_SCOPE) +endfunction() + +function(polysolve_has_linear_definition out_var definition) + polysolve_collect_linear_definitions(definitions) + if(definition IN_LIST definitions) + set(${out_var} ON PARENT_SCOPE) + else() + set(${out_var} OFF PARENT_SCOPE) + endif() +endfunction() + +function(polysolve_bool_var out_var name) + if(DEFINED ${name} AND ${${name}}) + set(${out_var} ON PARENT_SCOPE) + else() + set(${out_var} OFF PARENT_SCOPE) + endif() +endfunction() + +function(polysolve_check_expected_target name target) + set(expect_var "POLYSOLVE_EXPECT_${name}") + if(DEFINED ${expect_var} AND NOT "${${expect_var}}" STREQUAL "") + if(${${expect_var}} AND NOT TARGET ${target}) + message(FATAL_ERROR "${name}: expected target '${target}' to exist") + elseif(NOT ${${expect_var}} AND TARGET ${target}) + message(FATAL_ERROR "${name}: expected target '${target}' to be absent") + endif() + endif() +endfunction() + +function(polysolve_check_optional_solver name option target definition) + polysolve_bool_var(option_enabled ${option}) + polysolve_has_linear_definition(definition_enabled ${definition}) + polysolve_check_expected_target(${name} ${target}) + + if(option_enabled AND TARGET ${target}) + if(NOT definition_enabled) + message(FATAL_ERROR "${name}: target '${target}' exists but '${definition}' is missing") + endif() + message(STATUS "${name}: enabled via ${target}") + else() + if(definition_enabled) + message(FATAL_ERROR "${name}: '${definition}' is set without an available requested target") + endif() + if(option_enabled) + message(STATUS "${name}: disabled because ${target} is unavailable") + else() + message(STATUS "${name}: disabled by option") + endif() + endif() +endfunction() + +polysolve_check_optional_solver(ACCELERATE POLYSOLVE_WITH_ACCELERATE polysolve::accelerate POLYSOLVE_WITH_ACCELERATE) +polysolve_check_optional_solver(CHOLMOD POLYSOLVE_WITH_CHOLMOD SuiteSparse::CHOLMOD POLYSOLVE_WITH_CHOLMOD) +polysolve_check_optional_solver(UMFPACK POLYSOLVE_WITH_UMFPACK SuiteSparse::UMFPACK POLYSOLVE_WITH_UMFPACK) +polysolve_check_optional_solver(SUPERLU POLYSOLVE_WITH_SUPERLU SuperLU::SuperLU POLYSOLVE_WITH_SUPERLU) +polysolve_check_optional_solver(SPQR POLYSOLVE_WITH_SPQR SuiteSparse::SPQR POLYSOLVE_WITH_SPQR) +polysolve_check_optional_solver(MKL POLYSOLVE_WITH_MKL mkl::mkl POLYSOLVE_WITH_MKL) +polysolve_check_optional_solver(PARDISO POLYSOLVE_WITH_PARDISO Pardiso::Pardiso POLYSOLVE_WITH_PARDISO) +polysolve_check_optional_solver(HYPRE POLYSOLVE_WITH_HYPRE HYPRE::HYPRE POLYSOLVE_WITH_HYPRE) +polysolve_check_optional_solver(AMGCL POLYSOLVE_WITH_AMGCL amgcl::amgcl POLYSOLVE_WITH_AMGCL) +polysolve_check_optional_solver(SPECTRA POLYSOLVE_WITH_SPECTRA Spectra::Spectra POLYSOLVE_WITH_SPECTRA) +polysolve_check_optional_solver(CUSOLVER POLYSOLVE_WITH_CUSOLVER CUDA::cusolver POLYSOLVE_WITH_CUSOLVER)