Use FindCUDAToolkit for CMake >= 3.17

- Enables relocatable installs if the CUDA libraries are not installed
  in a location on the LD_LIBRARY_PATH.
- Also bump the minimum CMake version to 3.11 to reflect the issue
  reported in #903.

Change-Id: I333882b7238c76104d739c7054f29cc35cc4e919
diff --git a/CMakeLists.txt b/CMakeLists.txt
index b5d0efe..01f47b8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -29,7 +29,7 @@
 # Authors: keir@google.com (Keir Mierle)
 #          alexs.mac@gmail.com (Alex Stewart)
 
-cmake_minimum_required(VERSION 3.10)
+cmake_minimum_required(VERSION 3.11)
 
 if (POLICY CMP0074) # Added in CMake 3.12
   # FindTBB.cmake uses TBB_ROOT in a way that is historical, but also compliant
@@ -231,27 +231,69 @@
 endif (Eigen3_FOUND)
 
 if (USE_CUDA)
-  find_package(CUDA QUIET)
-  if (CUDA_FOUND)
-    message("-- Found CUDA version ${CUDA_VERSION}: "
-        "${CUDA_LIBRARIES};"
-        "${CUDA_cusolver_LIBRARY};"
-        "${CUDA_cusparse_LIBRARY}")
-    if(${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.18")
-      # Support Maxwell, Pascal, Volta, Turing, and Ampere GPUs.
-      set(CMAKE_CUDA_ARCHITECTURES "50;60;70;80")
-      message("-- Setting CUDA Architecture to ${CMAKE_CUDA_ARCHITECTURES}")
-    endif()
-    enable_language(CUDA)
-  else (CUDA_FOUND)
-    message("-- Did not find CUDA library, disabling CUDA support.")
-    update_cache_variable(USE_CUDA OFF)
-    list(APPEND CERES_COMPILE_OPTIONS CERES_NO_CUDA)
-  endif (CUDA_FOUND)
-else (USE_CUDA)
+  if (CMAKE_VERSION VERSION_LESS 3.17)
+    # On older versions of CMake (20.04 default is 3.16) FindCUDAToolkit was
+    # not available, but FindCUDA was deprecated. To avoid special-case handling
+    # elsewhere, emulate the effects of FindCUDAToolkit locally in terms of the
+    # expected CMake imported targets and defined variables. This can be removed
+    # from as soon as the min CMake version is >= 3.17.
+    find_package(CUDA QUIET)
+    if (CUDA_FOUND)
+      message("-- Found CUDA version ${CUDA_VERSION} installed in: "
+        "${CUDA_TOOLKIT_ROOT_DIR} via legacy (< 3.17) CMake module. "
+        "Using the legacy CMake module means that any installation of "
+        "Ceres will require that the CUDA libraries be installed in a "
+        "location included in the LD_LIBRARY_PATH.")
+      enable_language(CUDA)
+
+      macro(DECLARE_IMPORTED_CUDA_TARGET COMPONENT)
+        add_library(CUDA::${COMPONENT} INTERFACE IMPORTED)
+        target_include_directories(
+          CUDA::${COMPONENT} INTERFACE ${CUDA_INCLUDE_DIRS})
+        target_link_libraries(
+          CUDA::${COMPONENT} INTERFACE ${CUDA_${COMPONENT}_LIBRARY} ${ARGN})
+      endmacro()
+
+      declare_imported_cuda_target(cublas)
+      declare_imported_cuda_target(cusolver)
+      declare_imported_cuda_target(cusparse)
+      declare_imported_cuda_target(cudart ${CUDA_LIBRARIES})
+
+      set(CUDAToolkit_BIN_DIR ${CUDA_TOOLKIT_ROOT_DIR}/bin)
+
+    else (CUDA_FOUND)
+      message("-- Did not find CUDA, disabling CUDA support.")
+      update_cache_variable(USE_CUDA OFF)
+    endif (CUDA_FOUND)
+  else (CMAKE_VERSION VERSION_LESS 3.17)
+    find_package(CUDAToolkit QUIET)
+    if (CUDAToolkit_FOUND)
+      message("-- Found CUDA version ${CUDAToolkit_VERSION} installed in: "
+        "${CUDAToolkit_TARGET_DIR}")
+      set(CUDAToolkit_DEPENDENCY
+        "find_dependency(CUDAToolkit ${CUDAToolkit_VERSION})")
+      enable_language(CUDA)
+      if (CMAKE_VERSION VERSION_GREATER_EQUAL "3.18")
+        # Support Maxwell, Pascal, Volta, Turing, and Ampere GPUs.
+        set(CMAKE_CUDA_ARCHITECTURES "50;60;70;80")
+        message("-- Setting CUDA Architecture to ${CMAKE_CUDA_ARCHITECTURES}")
+      endif()
+      list(APPEND CERES_CUDA_LIBRARIES
+        CUDA::cublas
+        CUDA::cudart
+        CUDA::cusolver
+        CUDA::cusparse)
+    else (CUDAToolkit_FOUND)
+      message("-- Did not find CUDA, disabling CUDA support.")
+      update_cache_variable(USE_CUDA OFF)
+    endif (CUDAToolkit_FOUND)
+  endif (CMAKE_VERSION VERSION_LESS 3.17)
+endif (USE_CUDA)
+if (NOT USE_CUDA)
   message("-- Building without CUDA.")
   list(APPEND CERES_COMPILE_OPTIONS CERES_NO_CUDA)
-endif (USE_CUDA)
+endif (NOT USE_CUDA)
+
 if (LAPACK)
   find_package(LAPACK QUIET)
   if (LAPACK_FOUND)
diff --git a/cmake/CeresConfig.cmake.in b/cmake/CeresConfig.cmake.in
index 33d8078..bb6cc43 100644
--- a/cmake/CeresConfig.cmake.in
+++ b/cmake/CeresConfig.cmake.in
@@ -181,6 +181,7 @@
 @OpenMP_DEPENDENCY@
 @SuiteSparse_DEPENDENCY@
 @Threads_DEPENDENCY@
+@CUDAToolkit_DEPENDENCY@
 
 # As imported CMake targets are not re-exported when a dependent target is
 # exported, we must invoke find_package(XXX) here to reload the definition
diff --git a/internal/ceres/CMakeLists.txt b/internal/ceres/CMakeLists.txt
index 21caa8d..4a85e8b 100644
--- a/internal/ceres/CMakeLists.txt
+++ b/internal/ceres/CMakeLists.txt
@@ -140,20 +140,19 @@
 endif()
 
 if (USE_CUDA)
-  list(APPEND
-       CERES_LIBRARY_PRIVATE_DEPENDENCIES
-       ${CUDA_LIBRARIES}
-       ${CUDA_cublas_LIBRARY}
-       ${CUDA_cusolver_LIBRARY}
-       ${CUDA_cusparse_LIBRARY})
+  list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES
+    CUDA::cublas
+    CUDA::cudart
+    CUDA::cusolver
+    CUDA::cusparse)
   if (BUILD_TESTING AND GFLAGS)
     add_test(
         NAME cuda_memcheck_dense_qr_test
-        COMMAND ${CUDA_TOOLKIT_ROOT_DIR}/bin/cuda-memcheck --leak-check full
+        COMMAND ${CUDAToolkit_BIN_DIR}/cuda-memcheck --leak-check full
             $<TARGET_FILE:cuda_dense_qr_test>)
     add_test(
         NAME cuda_memcheck_dense_cholesky_test
-        COMMAND ${CUDA_TOOLKIT_ROOT_DIR}/bin/cuda-memcheck --leak-check full
+        COMMAND ${CUDAToolkit_BIN_DIR}/cuda-memcheck --leak-check full
             $<TARGET_FILE:cuda_dense_cholesky_test>)
   endif (BUILD_TESTING AND GFLAGS)
   set_source_files_properties(cuda_kernels.cu.cc PROPERTIES LANGUAGE CUDA)
@@ -393,10 +392,6 @@
   list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES_INCLUDE_DIRS
     ${AccelerateSparse_INCLUDE_DIRS})
 endif()
-if (USE_CUDA)
-  list(APPEND CERES_LIBRARY_PRIVATE_DEPENDENCIES_INCLUDE_DIRS
-    ${CUDA_INCLUDE_DIRS})
-endif()
 # Add include locations for optional dependencies to the Ceres target without
 # duplication.
 list(REMOVE_DUPLICATES CERES_LIBRARY_PRIVATE_DEPENDENCIES_INCLUDE_DIRS)
@@ -449,7 +444,11 @@
     target_include_directories(${NAME}_test
       PRIVATE ${Ceres_SOURCE_DIR}/internal/ceres
               ${CERES_LIBRARY_PRIVATE_DEPENDENCIES_INCLUDE_DIRS})
-    target_link_libraries(${NAME}_test PRIVATE gtest test_util ceres_static)
+    # Some tests include direct references/includes of private dependency
+    # headers which are not propagated via the ceres targets, so link them
+    # explicitly.
+    target_link_libraries(${NAME}_test PRIVATE gtest test_util ceres_static
+      ${CERES_LIBRARY_PRIVATE_DEPENDENCIES})
 
     # covariance_test uses SuiteSparseQR.hpp. However, since SuiteSparse import
     # targets are private (link only) dependencies not propagated to consumers,
@@ -572,8 +571,11 @@
   target_include_directories(${BENCHMARK_TARGET}
     PRIVATE ${Ceres_SOURCE_DIR}/internal
             ${CERES_LIBRARY_PRIVATE_DEPENDENCIES_INCLUDE_DIRS})
+  # Benchmarks include direct references/includes of private dependency headers
+  # which are not propagated via the ceres targets, so link them explicitly.
   target_link_libraries(${BENCHMARK_TARGET}
-    PRIVATE benchmark::benchmark ceres_static)
+    PRIVATE benchmark::benchmark ceres_static
+            ${CERES_LIBRARY_PRIVATE_DEPENDENCIES})
 endmacro()
 
 if (BUILD_BENCHMARKS)