Report Ceres compile options as components in find_package(). - Users can now specify particular components from Ceres, such as SuiteSparse support) that must be present in a detected version of Ceres in order for it to be reported as found by find_package(). - This allows users to specify for example that they require a version of Ceres with SuiteSparse support at configure time, rather than finding out only at run time that Ceres was not compiled with the options they require. - The list of available components are built directly from the Ceres compile options. - The meta-module SparseLinearAlgebraLibrary is present if at least one sparse linear algebra backend is available. Change-Id: I65f1ddfd7697e6dd25bb4ac7e54f5097d3ca6266
diff --git a/CMakeLists.txt b/CMakeLists.txt index 1e3ae33..cbb5021 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt
@@ -774,6 +774,12 @@ # path is present in CMAKE_MODULE_PATH when find_package(Ceres) is called, # the installed version is preferred. +# Build the list of Ceres components for CeresConfig.cmake from the current set +# of compile options. +include(CeresCompileOptionsToComponents) +ceres_compile_options_to_components("${CERES_COMPILE_OPTIONS}" + CERES_COMPILED_COMPONENTS) + # Create a CeresConfigVersion.cmake file containing the version information, # used by both export() & install(). configure_file("${CMAKE_SOURCE_DIR}/cmake/CeresConfigVersion.cmake.in"
diff --git a/cmake/CeresCompileOptionsToComponents.cmake b/cmake/CeresCompileOptionsToComponents.cmake new file mode 100644 index 0000000..7177475 --- /dev/null +++ b/cmake/CeresCompileOptionsToComponents.cmake
@@ -0,0 +1,91 @@ +# Ceres Solver - A fast non-linear least squares minimizer +# Copyright 2016 Google Inc. All rights reserved. +# http://ceres-solver.org/ +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# +# Authors: alexs.mac@gmail.com (Alex Stewart) +# + +# Conditionally add a value to the output list based on whether the specified +# value is found in the input list. +function(update_output_if_found INPUT_LIST_VAR OUTPUT_LIST_VAR ITEM_TO_FIND VAR_TO_COPY_IF_FOUND VAR_TO_COPY_IF_NOT_FOUND) + list(FIND ${INPUT_LIST_VAR} "${ITEM_TO_FIND}" HAVE_ITEM) + # list(FIND ..) returns -1 if the element was not in the list, but CMake + # interprets if (VAR) to be true if VAR is any non-zero number, even + # negative ones, hence we have to explicitly check for >= 0. + if (HAVE_ITEM GREATER -1) + list(APPEND ${OUTPUT_LIST_VAR} "${VAR_TO_COPY_IF_FOUND}") + else() + list(APPEND ${OUTPUT_LIST_VAR} "${VAR_TO_COPY_IF_NOT_FOUND}") + endif() + set(${OUTPUT_LIST_VAR} ${${OUTPUT_LIST_VAR}} PARENT_SCOPE) +endfunction() + +# Helpers for update_output_if_found() to improve legibility when dealing with +# USE_XXX & NO_XXX option types in ceres_compile_options_to_components(). +macro(add_to_output_if_found INPUT_LIST_VAR OUTPUT_LIST_VAR ITEM_TO_FIND VAR_TO_COPY_IF_FOUND) + update_output_if_found(${INPUT_LIST_VAR} + ${OUTPUT_LIST_VAR} + "${ITEM_TO_FIND}" + "${VAR_TO_COPY_IF_FOUND}" + "") # Copy nothing if not found. +endmacro() + +macro(add_to_output_if_not_found INPUT_LIST_VAR OUTPUT_LIST_VAR ITEM_TO_FIND VAR_TO_COPY_IF_NOT_FOUND) + update_output_if_found(${INPUT_LIST_VAR} + ${OUTPUT_LIST_VAR} + "${ITEM_TO_FIND}" + "" # Copy nothing if found + "${VAR_TO_COPY_IF_NOT_FOUND}") +endmacro() + +# Convert the Ceres compile options specified by: CURRENT_CERES_COMPILE_OPTIONS +# into the correponding list of Ceres components (names), which may be used in: +# find_package(Ceres COMPONENTS <XXX>). +function(ceres_compile_options_to_components CURRENT_CERES_COMPILE_OPTIONS CERES_COMPONENTS_VAR) + # To enable users to specify that they want *a* sparse linear algebra backend + # without having to specify explicitly which one, for each sparse library we + # add the 'meta-module': SparseLinearAlgebraLibrary in addition to their own + # module name. + add_to_output_if_found(CURRENT_CERES_COMPILE_OPTIONS ${CERES_COMPONENTS_VAR} + CERES_USE_EIGEN_SPARSE "EigenSparse;SparseLinearAlgebraLibrary") + add_to_output_if_not_found(CURRENT_CERES_COMPILE_OPTIONS ${CERES_COMPONENTS_VAR} + CERES_NO_LAPACK "LAPACK") + add_to_output_if_not_found(CURRENT_CERES_COMPILE_OPTIONS ${CERES_COMPONENTS_VAR} + CERES_NO_SUITESPARSE "SuiteSparse;SparseLinearAlgebraLibrary") + add_to_output_if_not_found(CURRENT_CERES_COMPILE_OPTIONS ${CERES_COMPONENTS_VAR} + CERES_NO_CXSPARSE "CXSparse;SparseLinearAlgebraLibrary") + add_to_output_if_not_found(CURRENT_CERES_COMPILE_OPTIONS ${CERES_COMPONENTS_VAR} + CERES_RESTRICT_SCHUR_SPECIALIZATION "SchurSpecializations") + add_to_output_if_found(CURRENT_CERES_COMPILE_OPTIONS ${CERES_COMPONENTS_VAR} + CERES_USE_CXX11 "C++11") + add_to_output_if_found(CURRENT_CERES_COMPILE_OPTIONS ${CERES_COMPONENTS_VAR} + CERES_USE_OPENMP "OpenMP") + # Remove duplicates of SparseLinearAlgebraLibrary if multiple sparse backends + # are present. + list(REMOVE_DUPLICATES ${CERES_COMPONENTS_VAR}) + set(${CERES_COMPONENTS_VAR} "${${CERES_COMPONENTS_VAR}}" PARENT_SCOPE) +endfunction()
diff --git a/cmake/CeresConfig.cmake.in b/cmake/CeresConfig.cmake.in index 9e29d7a..cdb3797 100644 --- a/cmake/CeresConfig.cmake.in +++ b/cmake/CeresConfig.cmake.in
@@ -94,6 +94,19 @@ return() endmacro(CERES_REPORT_NOT_FOUND) +# ceres_pretty_print_cmake_list( OUTPUT_VAR [item1 [item2 ... ]] ) +# +# Sets ${OUTPUT_VAR} in the caller's scope to a human-readable string +# representation of the list passed as the remaining arguments formed +# as: "[item1, item2, ..., itemN]". +function(ceres_pretty_print_cmake_list OUTPUT_VAR) + string(REPLACE ";" ", " PRETTY_LIST_STRING "[${ARGN}]") + set(${OUTPUT_VAR} "${PRETTY_LIST_STRING}" PARENT_SCOPE) +endfunction() + +# The list of (optional) components this version of Ceres was compiled with. +set(CERES_COMPILED_COMPONENTS "@CERES_COMPILED_COMPONENTS@") + # If Ceres was not installed, then by definition it was exported # from a build directory. set(CERES_WAS_INSTALLED @SETUP_CERES_CONFIG_FOR_INSTALLATION@) @@ -286,15 +299,53 @@ # Reset CMake module path to its state when this script was called. set(CMAKE_MODULE_PATH ${CALLERS_CMAKE_MODULE_PATH}) +# Build the detected Ceres version string to correctly capture whether it +# was installed, or exported. +ceres_pretty_print_cmake_list(CERES_COMPILED_COMPONENTS_STRING + ${CERES_COMPILED_COMPONENTS}) +if (CERES_WAS_INSTALLED) + set(CERES_DETECTED_VERSION_STRING "Ceres version: ${CERES_VERSION} " + "installed in: ${CURRENT_ROOT_INSTALL_DIR} with components: " + "${CERES_COMPILED_COMPONENTS_STRING}") +else (CERES_WAS_INSTALLED) + set(CERES_DETECTED_VERSION_STRING "Ceres version: ${CERES_VERSION} " + "exported from build directory: ${CERES_EXPORTED_BUILD_DIR} with " + "components: ${CERES_COMPILED_COMPONENTS_STRING}") +endif() + +# If the user called this script through find_package() whilst specifying +# particular Ceres components that should be found via: +# find_package(Ceres COMPONENTS XXX YYY), check the requested components against +# those with which Ceres was compiled. In this case, we should only report +# Ceres as found if all the requested components have been found. +if (Ceres_FIND_COMPONENTS) + foreach (REQUESTED_COMPONENT ${Ceres_FIND_COMPONENTS}) + list(FIND CERES_COMPILED_COMPONENTS ${REQUESTED_COMPONENT} HAVE_REQUESTED_COMPONENT) + # list(FIND ..) returns -1 if the element was not in the list, but CMake + # interprets if (VAR) to be true if VAR is any non-zero number, even + # negative ones, hence we have to explicitly check for >= 0. + if (HAVE_REQUESTED_COMPONENT EQUAL -1) + # Check for the presence of all requested components before reporting + # not found, such that we report all of the missing components rather + # than just the first. + list(APPEND MISSING_CERES_COMPONENTS ${REQUESTED_COMPONENT}) + endif() + endforeach() + if (MISSING_CERES_COMPONENTS) + ceres_pretty_print_cmake_list(REQUESTED_CERES_COMPONENTS_STRING + ${Ceres_FIND_COMPONENTS}) + ceres_pretty_print_cmake_list(MISSING_CERES_COMPONENTS_STRING + ${MISSING_CERES_COMPONENTS}) + ceres_report_not_found("Missing requested Ceres components: " + "${MISSING_CERES_COMPONENTS_STRING} (components requested: " + "${REQUESTED_CERES_COMPONENTS_STRING}). Detected " + "${CERES_DETECTED_VERSION_STRING}.") + endif() +endif() + # As we use CERES_REPORT_NOT_FOUND() to abort, if we reach this point we have # found Ceres and all required dependencies. -if (CERES_WAS_INSTALLED) - message(STATUS "Found Ceres version: ${CERES_VERSION} " - "installed in: ${CURRENT_ROOT_INSTALL_DIR}") -else (CERES_WAS_INSTALLED) - message(STATUS "Found Ceres version: ${CERES_VERSION} " - "exported from build directory: ${CERES_EXPORTED_BUILD_DIR}") -endif() +message(STATUS "Found " ${CERES_DETECTED_VERSION_STRING}) # Set CERES_FOUND to be equivalent to Ceres_FOUND, which is set to # TRUE by FindPackage() if this file is found and run, and after which
diff --git a/docs/source/building.rst b/docs/source/building.rst index 03f6dfd..2cff835 100644 --- a/docs/source/building.rst +++ b/docs/source/building.rst
@@ -736,6 +736,53 @@ Ceres was exported, then ``Ceres_DIR`` should be the path to the exported Ceres build directory. +Specify Ceres components +------------------------------------- + +You can specify particular Ceres components that you require (in order for Ceres +to be reported as found) when invoking ``find_package(Ceres)``. This allows you +to specify, for example, that you require a version of Ceres built with +SuiteSparse support. By definition, if you do not specify any components when +calling ``find_package(Ceres)`` (the default) any version of Ceres detected will +be reported as found, irrespective of which components it was built with. + +The Ceres components which can be specified are: + +#. ``LAPACK``: Ceres built using LAPACK (``LAPACK=ON``). + +#. ``SuiteSparse``: Ceres built with SuiteSparse (``SUITESPARSE=ON``). + +#. ``CXSparse``: Ceres built with CXSparse (``CXSPARSE=ON``). + +#. ``EigenSparse``: Ceres built with Eigen's sparse Cholesky factorization + (``EIGENSPARSE=ON``). + +#. ``SparseLinearAlgebraLibrary``: Ceres built with *at least one* sparse linear + algebra library. This is equivalent to ``SuiteSparse`` **OR** ``CXSparse`` + **OR** ``EigenSparse``. + +#. ``SchurSpecializations``: Ceres built with Schur specializations + (``SCHUR_SPECIALIZATIONS=ON``). + +#. ``OpenMP``: Ceres built with OpenMP (``OPENMP=ON``). + +#. ``C++11``: Ceres built with C++11 (``CXX11=ON``). + +To specify one/multiple Ceres components use the ``COMPONENTS`` argument to +`find_package() +<http://www.cmake.org/cmake/help/v3.2/command/find_package.html>`_ like so: + +.. code-block:: cmake + + # Find a version of Ceres compiled with SuiteSparse & EigenSparse support. + # + # NOTE: This will report Ceres as **not** found if the detected version of + # Ceres was not compiled with both SuiteSparse & EigenSparse. + # Remember, if you have multiple versions of Ceres installed, you + # can use Ceres_DIR to specify which should be used. + find_package(Ceres REQUIRED COMPONENTS SuiteSparse EigenSparse) + + Specify Ceres version ---------------------