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
---------------------