Refactor Ceres threading option configuration.

- Previously we had separate variables for each of the threading
  backends, each of which were made mutually exclusive via
  cmake_dependent_option().  This has unfortunate side-effects when
  trying to disable options if they are not currently enabled, in which
  case they are not defined.
- As all the threading options are mutually exclusive, this replaces
  all threading option variables with a single variable: CERES_THREADS,
  which is constrained to take the value of only the available threading
  backends.

Change-Id: I0822eefbac9a30772907b7732add365b37cc8ca0
diff --git a/CMakeLists.txt b/CMakeLists.txt
index f1f59ff..1ffcb2b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -115,7 +115,22 @@
 
 enable_testing()
 
-include(CMakeDependentOption)
+include(CeresThreadingModels)
+include(PrettyPrintCMakeList)
+find_available_ceres_threading_models(CERES_THREADING_MODELS_AVAILABLE)
+pretty_print_cmake_list(PRETTY_CERES_THREADING_MODELS_AVAILABLE
+  ${CERES_THREADING_MODELS_AVAILABLE})
+message("-- Detected available Ceres threading models: "
+  "${PRETTY_CERES_THREADING_MODELS_AVAILABLE}")
+set(CERES_THREADING_MODEL "${CERES_THREADING_MODEL}" CACHE STRING
+  "Ceres threading back-end" FORCE)
+if (NOT CERES_THREADING_MODEL)
+  list(GET CERES_THREADING_MODELS_AVAILABLE 0 DEFAULT_THREADING_MODEL)
+  update_cache_variable(CERES_THREADING_MODEL ${DEFAULT_THREADING_MODEL})
+endif()
+set_property(CACHE CERES_THREADING_MODEL PROPERTY STRINGS
+  ${CERES_THREADING_MODELS_AVAILABLE})
+
 option(MINIGLOG "Use a stripped down version of glog." OFF)
 option(GFLAGS "Enable Google Flags." ON)
 option(SUITESPARSE "Enable SuiteSparse." ON)
@@ -128,18 +143,6 @@
 option(CUSTOM_BLAS
        "Use handcoded BLAS routines (usually faster) instead of Eigen."
        ON)
-# Multithreading using OpenMP
-cmake_dependent_option(
-  OPENMP "Enable threaded solving in Ceres (requires OpenMP)" ON
-  "NOT TBB;NOT CXX11_THREADS" OFF)
-# Multithreading using TBB
-cmake_dependent_option(
-  TBB "Enable threaded solving in Ceres with TBB (requires TBB and C++11)" OFF
-  "NOT OPENMP;NOT CXX11_THREADS" OFF)
-# Multithreading using C++11 primitives.
-cmake_dependent_option(
-  CXX11_THREADS "Enable threaded solving in Ceres with C++11 primitives" OFF
-  "NOT OPENMP;NOT TBB" OFF)
 # Enable the use of Eigen as a sparse linear algebra library for
 # solving the nonlinear least squares problems.
 option(EIGENSPARSE "Enable Eigen as a sparse linear algebra library." ON)
@@ -412,46 +415,7 @@
   message("-- Disabling custom blas")
 endif (NOT CUSTOM_BLAS)
 
-if (OPENMP)
-  # Find quietly, as we can continue without OpenMP if it is not found.
-  find_package(OpenMP QUIET)
-  if (OPENMP_FOUND)
-    message("-- Building with OpenMP.")
-    list(APPEND CERES_COMPILE_OPTIONS CERES_USE_OPENMP)
-    set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
-    set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
-  else (OPENMP_FOUND)
-    message("-- Failed to find OpenMP, disabling. This is expected on "
-      "Clang < 3.8, and at least Xcode <= 8.  See Ceres documentation for "
-      "instructions to build with LLVM from Homebrew to enable OpenMP on OS X.")
-    update_cache_variable(OPENMP OFF)
-  endif (OPENMP_FOUND)
-else (OPENMP)
-  message("-- Building without OpenMP, disabling.")
-endif (OPENMP)
-
-if (TBB)
-  find_package(TBB QUIET)
-  if (TBB_FOUND)
-    message("-- Building with TBB (version: ${TBB_VERSION}).")
-    list(APPEND CERES_COMPILE_OPTIONS CERES_USE_TBB)
-    include_directories(${TBB_INCLUDE_DIRS})
-  else (TBB_FOUND)
-    message("-- Failed to find TBB, disabling.")
-    update_cache_variable(TBB OFF)
-  endif (TBB_FOUND)
-endif()
-
-if (CXX11_THREADS)
-  message("-- Building with C++11 threads.")
-  list(APPEND CERES_COMPILE_OPTIONS CERES_USE_CXX11_THREADS)
-endif()
-
-if (NOT OPENMP AND NOT TBB AND NOT CXX11_THREADS)
-  message("-- Neither OpenMP, TBB or C++11 threads is enabled, "
-    "disabling multithreading.")
-  list(APPEND CERES_COMPILE_OPTIONS CERES_NO_THREADS)
-endif (NOT OPENMP AND NOT TBB AND NOT CXX11_THREADS)
+set_ceres_threading_model("${CERES_THREADING_MODEL}")
 
 if (BUILD_BENCHMARKS)
   find_package(benchmark QUIET)
diff --git a/cmake/CeresThreadingModels.cmake b/cmake/CeresThreadingModels.cmake
new file mode 100644
index 0000000..910f4a8
--- /dev/null
+++ b/cmake/CeresThreadingModels.cmake
@@ -0,0 +1,94 @@
+# Ceres Solver - A fast non-linear least squares minimizer
+# Copyright 2018 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.
+#
+# Author: alexs.mac@gmail.com (Alex Stewart)
+
+# Ordered by expected preference.
+set(CERES_THREADING_MODELS "CXX11_THREADS;OPENMP;TBB;NO_THREADS")
+
+function(find_available_ceres_threading_models CERES_THREADING_MODELS_AVAILABLE_VAR)
+  set(CERES_THREADING_MODELS_AVAILABLE ${CERES_THREADING_MODELS})
+  # Remove any threading models for which the dependencies are not available.
+  find_package(OpenMP QUIET)
+  if (NOT OPENMP_FOUND)
+    list(REMOVE_ITEM CERES_THREADING_MODELS_AVAILABLE "OPENMP")
+  endif()
+  find_package(TBB QUIET)
+  if (NOT TBB_FOUND)
+    list(REMOVE_ITEM CERES_THREADING_MODELS_AVAILABLE "TBB")
+  endif()
+  if (NOT CERES_THREADING_MODELS_AVAILABLE)
+    # At least NO_THREADS should never be removed.  This check is purely
+    # protective against future threading model updates.
+    message(FATAL_ERROR "Ceres bug: Removed all threading models.")
+  endif()
+  set(${CERES_THREADING_MODELS_AVAILABLE_VAR}
+    ${CERES_THREADING_MODELS_AVAILABLE} PARENT_SCOPE)
+endfunction()
+
+macro(set_ceres_threading_model_to_cxx11_threads)
+  list(APPEND CERES_COMPILE_OPTIONS CERES_USE_CXX11_THREADS)
+endmacro()
+
+macro(set_ceres_threading_model_to_openmp)
+  find_package(OpenMP REQUIRED)
+  list(APPEND CERES_COMPILE_OPTIONS CERES_USE_OPENMP)
+  set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${OpenMP_CXX_FLAGS}")
+  set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${OpenMP_C_FLAGS}")
+endmacro()
+
+macro(set_ceres_threading_model_to_tbb)
+  find_package(TBB REQUIRED)
+  list(APPEND CERES_COMPILE_OPTIONS CERES_USE_TBB)
+  include_directories(${TBB_INCLUDE_DIRS})
+endmacro()
+
+macro(set_ceres_threading_model_to_no_threads)
+  list(APPEND CERES_COMPILE_OPTIONS CERES_NO_THREADS)
+endmacro()
+
+macro(set_ceres_threading_model CERES_THREADING_MODEL_TO_SET)
+  if ("${CERES_THREADING_MODEL_TO_SET}" STREQUAL "CXX11_THREADS")
+    set_ceres_threading_model_to_cxx11_threads()
+  elseif ("${CERES_THREADING_MODEL_TO_SET}" STREQUAL "OPENMP")
+    set_ceres_threading_model_to_openmp()
+  elseif ("${CERES_THREADING_MODEL_TO_SET}" STREQUAL "TBB")
+    set_ceres_threading_model_to_tbb()
+  elseif ("${CERES_THREADING_MODEL_TO_SET}" STREQUAL "NO_THREADS")
+    set_ceres_threading_model_to_no_threads()
+  else()
+    include(PrettyPrintCMakeList)
+    find_available_ceres_threading_models(_AVAILABLE_THREADING_MODELS)
+    pretty_print_cmake_list(
+      _AVAILABLE_THREADING_MODELS ${_AVAILABLE_THREADING_MODELS})
+    message(FATAL_ERROR "Unknown threading model specified: "
+      "'${CERES_THREADING_MODEL_TO_SET}'. Available threading models for "
+      "this platform are: ${_AVAILABLE_THREADING_MODELS}")
+  endif()
+  message("-- Using Ceres threading model: ${CERES_THREADING_MODEL_TO_SET}")
+endmacro()
diff --git a/cmake/PrettyPrintCMakeList.cmake b/cmake/PrettyPrintCMakeList.cmake
new file mode 100644
index 0000000..067883c
--- /dev/null
+++ b/cmake/PrettyPrintCMakeList.cmake
@@ -0,0 +1,39 @@
+# Ceres Solver - A fast non-linear least squares minimizer
+# Copyright 2018 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)
+
+# 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(pretty_print_cmake_list OUTPUT_VAR)
+  string(REPLACE ";" ", " PRETTY_LIST_STRING "[${ARGN}]")
+  set(${OUTPUT_VAR} "${PRETTY_LIST_STRING}" PARENT_SCOPE)
+endfunction()
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index e7951bf..b80dc25 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -664,13 +664,10 @@
    gains in the ``SPARSE_SCHUR`` solver, you can disable some of the
    template specializations by turning this ``OFF``.
 
-#. ``OPENMP [Default: ON]``: On certain platforms like Android,
-   multi-threading with ``OpenMP`` is not supported. Turn this ``OFF``
-   to disable multi-threading.
-
-#. ``TBB [Default: OFF]``: An alternative to ``OpenMP`` threading library that
-   uses Intel's Thread Building Blocks.  This option is mutually
-   exclusive to ``OPENMP`` and ``CXX11_THREADS``.
+#. ``CERES_THREADING_MODEL [Default: CXX11_THREADS > OPENMP > TBB > NO_THREADS]``:
+   Multi-threading backend Ceres should be compiled with.  This will
+   automatically be set to only accept the available subset of threading
+   options in the CMake GUI.
 
    .. NOTE::
 
@@ -678,10 +675,6 @@
       GPL/Commercial terms.  From 2017.x versions onwards, TBB is licensed under
       the Apache 2.0 license (and commerical terms).
 
-#. ``CXX11_THREADS [Default: OFF]``: An alternative to ``OpenMP``
-   threading library that uses a C++11 thread-pool.  This option
-   is mutually exclusive to ``OPENMP`` and ``TBB``.
-
 #. ``BUILD_SHARED_LIBS [Default: OFF]``: By default Ceres is built as
    a static library, turn this ``ON`` to instead build Ceres as a
    shared library.
@@ -859,12 +852,13 @@
 #. ``SchurSpecializations``: Ceres built with Schur specializations
    (``SCHUR_SPECIALIZATIONS=ON``).
 
-#. ``OpenMP``: Ceres built with OpenMP (``OPENMP=ON``).
+#. ``OpenMP``: Ceres built with OpenMP (``CERES_THREADING_MODEL=OPENMP``).
 
-#. ``TBB``: Ceres built with Intel Thread Building Blocks (TBB) (``TBB=ON``).
+#. ``TBB``: Ceres built with Intel Thread Building Blocks (TBB)
+   (``CERES_THREADING_MODEL=TBB``).
 
 #. ``Multithreading``: Ceres built with *a* multithreading library.
-   This is equivalent to ``OpenMP`` **OR** ``TBB``.
+   This is equivalent to (``CERES_THREAD != NO_THREADS``).
 
 #. ``C++11``: Ceres built with C++11.
 
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt
index 0532073..71996b6 100644
--- a/internal/ceres/CMakeLists.txt
+++ b/internal/ceres/CMakeLists.txt
@@ -30,13 +30,27 @@
 
 # Avoid 'xxx.cc has no symbols' warnings from source files which are 'empty'
 # when their enclosing #ifdefs are disabled.
-if (CXX11_THREADS)
+find_package(Threads QUIET)
+if (CERES_THREADING_MODEL STREQUAL "CXX11_THREADS")
   set(CERES_PARALLEL_FOR_SRC parallel_for_cxx.cc thread_pool.cc)
-elseif (OPENMP)
+elseif (CERES_THREADING_MODEL STREQUAL "OPENMP")
   set(CERES_PARALLEL_FOR_SRC parallel_for_openmp.cc)
-elseif (TBB)
+  if (CMAKE_COMPILER_IS_GNUCXX)
+    # OpenMP in GCC requires the GNU OpenMP library.
+    list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES gomp)
+  endif()
+  if (NOT MSVC)
+    # Add thread library dependencies for OpenMP to the Ceres target.
+    list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES ${CMAKE_THREAD_LIBS_INIT})
+  endif()
+elseif (CERES_THREADING_MODEL STREQUAL "TBB")
   set(CERES_PARALLEL_FOR_SRC parallel_for_tbb.cc)
-else()
+  list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES ${TBB_LIBRARIES})
+  if (NOT MSVC)
+    # Add thread library dependencies for OpenMP to the Ceres target.
+    list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES ${CMAKE_THREAD_LIBS_INIT})
+  endif (NOT MSVC)
+elseif (CERES_THREADING_MODEL STREQUAL "NO_THREADS")
   set(CERES_PARALLEL_FOR_SRC parallel_for_nothreads.cc)
 endif()
 
@@ -193,23 +207,6 @@
   list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES ${LAPACK_LIBRARIES})
 endif ()
 
-if (OPENMP_FOUND)
-  # OpenMP support in Clang requires a non-GNU OpenMP library.
-  if (CMAKE_COMPILER_IS_GNUCXX)
-    list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES gomp)
-  endif()
-  if (NOT MSVC)
-    list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES ${CMAKE_THREAD_LIBS_INIT})
-  endif()
-endif (OPENMP_FOUND)
-
-if (TBB_FOUND)
-  list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES ${TBB_LIBRARIES})
-  if (NOT MSVC)
-    list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES ${CMAKE_THREAD_LIBS_INIT})
-  endif (NOT MSVC)
-endif (TBB_FOUND)
-
 set(CERES_LIBRARY_SOURCE
     ${CERES_INTERNAL_SRC}
     ${CERES_INTERNAL_HDRS}