Reland On Demand Downloading of CUDA Dependencies for non CI Builds (#9519)

* Revert "Revert "Implement On Demand Downloading of CUDA Dependencies for non CI Builds" (#9514)"

This reverts commit f40d66a5aa2ad221ec43032be1c67b3a647c0ab3.

* Update CMakeLists.txt

Co-authored-by: Geoffrey Martin-Noble <gcmn@google.com>

Co-authored-by: Geoffrey Martin-Noble <gcmn@google.com>
diff --git a/CMakeLists.txt b/CMakeLists.txt
index db5e928..7384a69 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -433,13 +433,21 @@
 if(IREE_TARGET_BACKEND_CUDA OR IREE_HAL_DRIVER_CUDA)
   find_package(CUDAToolkit)
 
-  # We define the magic IREE_CUDA_DEPS_DIR env var in our CI docker images if we
-  # have a stripped down CUDA toolkit suitable for compiling available. We
-  # trigger on this below as a fallback for locating headers and libdevice
-  # files.
-  if(NOT CUDAToolkit_FOUND AND DEFINED ENV{IREE_CUDA_DEPS_DIR})
-    set(CUDAToolkit_ROOT "$ENV{IREE_CUDA_DEPS_DIR}")
-    message(STATUS "CUDA SDK not found by CMake but using IREE_CUDA_DEPS = ${CUDAToolkit_ROOT}")
+  if(NOT CUDAToolkit_FOUND)
+    if(DEFINED ENV{IREE_CUDA_DEPS_DIR})
+      # We define the magic IREE_CUDA_DEPS_DIR env var in our CI docker images if we
+      # have a stripped down CUDA toolkit suitable for compiling available. We
+      # trigger on this below as a fallback for locating headers and libdevice
+      # files.
+      set(CUDAToolkit_ROOT "$ENV{IREE_CUDA_DEPS_DIR}")
+      message(STATUS "CUDA SDK not found by CMake but using IREE_CUDA_DEPS = ${CUDAToolkit_ROOT}")
+    else()
+      # If we haven't found CUDA deps, download at least enough to build for CUDA.
+      # This will define IREE_CUDA_DOWNLOAD_LIBDEVICE_PATH & IREE_CUDA_DOWNLOAD_INCLUDE_PATH
+      # vars with the target deps.
+      message(STATUS "CUDA SDK not found by CMake but downloading dependencies")
+      add_subdirectory(build_tools/third_party/cuda EXCLUDE_FROM_ALL)
+    endif()
   endif()
 endif()
 
@@ -461,6 +469,9 @@
     # are hard and such. In this case, if the user went to the trouble to
     # tell us where it is, we have enough information.
     set(IREE_CUDA_LIBDEVICE_PATH "${CUDAToolkit_ROOT}/nvvm/libdevice/libdevice.10.bc")
+  elseif(IREE_CUDA_DOWNLOAD_LIBDEVICE_PATH)
+    message(STATUS "Using downloaded CUDA libdevice")
+    set(IREE_CUDA_LIBDEVICE_PATH "${IREE_CUDA_DOWNLOAD_LIBDEVICE_PATH}")
   else()
     message(FATAL_ERROR "Building with IREE_TARGET_BACKEND_CUDA requires either a CUDA SDK (consult CMake docs for your version: https://cmake.org/cmake/help/latest/module/FindCUDAToolkit.html) or an explicit path to libdevice (set with -DIREE_CUDA_LIBDEVICE_PATH=/path/to/libdevice.10.bc)")
   endif()
@@ -485,6 +496,9 @@
     else()
       message(SEND_ERROR "Using explicitly specified CUDAToolkit_ROOT, could not find cuda.h at: ${CUDAToolkit_INCLUDE_DIRS}")
     endif()
+  elseif(IREE_CUDA_DOWNLOAD_INCLUDE_PATH)
+    message(STATUS "Using downloaded CUDA includes")
+    set(CUDAToolkit_INCLUDE_DIRS "${IREE_CUDA_DOWNLOAD_INCLUDE_PATH}")
   else()
     message(SEND_ERROR "Cannot build IREE runtime CUDA components (-DIREE_HAL_DRIVER_CUDA=ON) because a CUDA SDK was not found. Consult CMake docs for your version: https://cmake.org/cmake/help/latest/module/FindCUDAToolkit.html")
   endif()
diff --git a/build_tools/third_party/cuda/CMakeLists.txt b/build_tools/third_party/cuda/CMakeLists.txt
new file mode 100644
index 0000000..12bffec
--- /dev/null
+++ b/build_tools/third_party/cuda/CMakeLists.txt
@@ -0,0 +1,78 @@
+# Copyright 2022 The IREE Authors
+#
+# Licensed under the Apache License v2.0 with LLVM Exceptions.
+# See https://llvm.org/LICENSE.txt for license information.
+# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+
+set(_TARGET_DIR ${CMAKE_CURRENT_BINARY_DIR})
+set(_DOWNLOAD_SCRIPT_URL "https://raw.githubusercontent.com/NVIDIA/build-system-archive-import-examples/44dfb51fad75a8a2f1044a4fe221aba70571b86f/parse_redist.py")
+set(_DOWNLOAD_DIR ${_TARGET_DIR}/download)
+set(_DOWNLOAD_SCRIPT_PATH ${_DOWNLOAD_DIR}/parse_redist.py)
+
+# Parameters to the download script.
+# Look for an appropriate redistrib_*.json here to verify:
+#   https://developer.download.nvidia.com/compute/cuda/redist/
+set(_VERSION "11.6.2")
+set(_PRODUCT "cuda")
+if(UNIX)
+  set(_OS "linux")
+elseif(WIN32)
+  set(_OS "windows")
+else()
+  message(SEND_ERROR "Unsupported OS environment. Must be Windows or Linux.")
+  return()
+endif()
+# CUDA is only supported on Linux/Windows where x64 is the only arch for now.
+set(_ARCH "x86_64")
+
+# Components that we need to fetch.
+set(_COMPONENTS_FO_FETCH "")
+list(APPEND _COMPONENTS_FO_FETCH "cuda_nvcc")
+list(APPEND _COMPONENTS_FO_FETCH "cuda_cudart")
+
+# Paths within the arch specific installation that we want to retain.
+set(_RETAIN_PATHS "")
+list(APPEND _RETAIN_PATHS "LICENSE")
+list(APPEND _RETAIN_PATHS "nvvm/libdevice/libdevice.10.bc")
+list(APPEND _RETAIN_PATHS "include/cuda.h")
+
+message(STATUS "Extracting to ${_TARGET_DIR}")
+file(MAKE_DIRECTORY ${_DOWNLOAD_DIR})
+
+# First fetch the download script to the tmp dir.
+file(DOWNLOAD ${_DOWNLOAD_SCRIPT_URL} ${_DOWNLOAD_SCRIPT_PATH})
+
+# Then use the download script to fetch and flatten each component we want
+# into the tmp dir.
+# This will produce a unified directory tree under:
+#   flat/$OS-$ARCH
+set(SRC_DIR "${_DOWNLOAD_DIR}/${_OS}-${_ARCH}")
+foreach(COMPONENT ${_COMPONENTS_FO_FETCH})
+  message(STATUS "Downloading component ${COMPONENT}")
+  execute_process(COMMAND ${Python3_EXECUTABLE} "${_DOWNLOAD_SCRIPT_PATH}"
+    --label "${_VERSION}"
+    --product "${_PRODUCT}"
+    --os "${_OS}"
+    --arch "${_ARCH}"
+    --component "${COMPONENT}"
+    --output "${_DOWNLOAD_DIR}")
+endforeach()
+
+if(NOT EXISTS "${SRC_DIR}")
+  message(FATAL_ERROR "Download did not produce expected source dir: ${SRC_DIR}")
+  return()
+endif()
+
+foreach(REL_PATH ${_RETAIN_PATHS})
+  set(SRC_FILE "${SRC_DIR}/${REL_PATH}")
+  set(TARGET_FILE "${_TARGET_DIR}/${REL_PATH}")
+  message(STATUS "Copy ${SRC_FILE} -> ${TARGET_FILE}")
+  file(COPY ${SRC_FILE} DESTINATION ${TARGET_FILE})
+endforeach()
+
+# Delete tmp directory.
+file(REMOVE_RECURSE ${_DOWNLOAD_DIR})
+
+# Set vars for downloaded cuda deps
+set(IREE_CUDA_DOWNLOAD_LIBDEVICE_PATH "${_TARGET_DIR}/nvvm/libdevice/libdevice.10.bc" PARENT_SCOPE)
+set(IREE_CUDA_DOWNLOAD_INCLUDE_PATH "${_TARGET_DIR}/include" PARENT_SCOPE)