diff --git a/CMakeLists.txt b/CMakeLists.txt
index a62ef6bbc3bb..0cb939a21848 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -106,13 +106,6 @@ else ()
     set(Halide_ANY_SANITIZERS_ENABLED 0)
 endif ()
 
-# Enable the SPIR-V target if requested (must declare before processing dependencies)
-option(TARGET_SPIRV "Include SPIR-V target" OFF)
-option(TARGET_VULKAN "Include Vulkan target" ON)
-if (TARGET_VULKAN)
-    set(TARGET_SPIRV ON) # required
-endif()
-
 # Helper function to set C++ compiler warnings in a sane way
 function(set_halide_compiler_warnings NAME)
     target_compile_options(
@@ -193,8 +186,10 @@ endfunction()
 option(THREADS_PREFER_PTHREAD_FLAG "When enabled, prefer to use the -pthread flag to explicit linking" ON)
 find_package(Threads REQUIRED)
 
-## Complex dependencies
-add_subdirectory(dependencies/llvm)
+## LLVM
+find_package(Halide_LLVM 17...20 REQUIRED
+             COMPONENTS WebAssembly X86
+             OPTIONAL_COMPONENTS AArch64 AMDGPU ARM Hexagon NVPTX PowerPC RISCV)
 
 ## Image formats
 
diff --git a/CMakePresets.json b/CMakePresets.json
index 84eac16d30c5..9eaa883f8ca9 100644
--- a/CMakePresets.json
+++ b/CMakePresets.json
@@ -69,7 +69,7 @@
       "displayName": "Debian (Debug)",
       "description": "Debug build assuming Debian-provided dependencies",
       "cacheVariables": {
-        "Halide_SHARED_LLVM": "ON"
+        "Halide_LLVM_SHARED_LIBS": "ON"
       }
     },
     {
@@ -109,7 +109,7 @@
       "inherits": "release",
       "cacheVariables": {
         "CMAKE_PREFIX_PATH": "/opt/homebrew;/opt/homebrew/opt/llvm;/opt/homebrew/opt/jpeg",
-        "Halide_SHARED_LLVM": "YES"
+        "Halide_LLVM_SHARED_LIBS": "YES"
       }
     },
     {
diff --git a/README_cmake.md b/README_cmake.md
index 60adfc76d61d..558fd1fd5fff 100644
--- a/README_cmake.md
+++ b/README_cmake.md
@@ -392,7 +392,7 @@ compiled.
 |------------------------------------------|-----------------------|------------------------------------------------------------------------------------------------------------------|
 | [`BUILD_SHARED_LIBS`][build_shared_libs] | `ON`                  | Standard CMake variable that chooses whether to build as a static or shared library.                             |
 | `Halide_BUNDLE_LLVM`                     | `OFF`                 | When building Halide as a static library, unpack the LLVM static libraries and add those objects to libHalide.a. |
-| `Halide_SHARED_LLVM`                     | `OFF`                 | Link to the shared version of LLVM. Not available on Windows.                                                    |
+| `Halide_LLVM_SHARED_LIBS`                | `OFF`                 | Link to the shared version of LLVM. Not available on Windows.                                                    |
 | `Halide_ENABLE_RTTI`                     | _inherited from LLVM_ | Enable RTTI when building Halide. Recommended to be set to `ON`                                                  |
 | `Halide_ENABLE_EXCEPTIONS`               | `ON`                  | Enable exceptions when building Halide                                                                           |
 | `Halide_TARGET`                          | _empty_               | The default target triple to use for `add_halide_library` (and the generator tests, by extension)                |
@@ -589,7 +589,7 @@ If Halide is not globally installed, you will need to add the root of the Halide
 installation directory to [`CMAKE_PREFIX_PATH`][cmake_prefix_path] at the CMake
 command line.
 
-```
+```console
 dev@ubuntu:~/myproj$ cmake -G Ninja -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH="/path/to/Halide-install" -S . -B build
 ```
 
@@ -784,10 +784,10 @@ Variables set by the package:
 
 Variables that control package behavior:
 
-| Variable                   | Description |
-|----------------------------|-------------|
-| `Halide_PYTHON_LAUNCHER`   | Semicolon separated list containing a command to launch the Python interpreter. Can be used to set environment variables for Python generators. |
-| `Halide_NO_DEFAULT_FLAGS`  | Off by default. When enabled, suppresses recommended compiler flags that would be added by `add_halide_generator` |
+| Variable                  | Description                                                                                                                                     |
+|---------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------|
+| `Halide_PYTHON_LAUNCHER`  | Semicolon separated list containing a command to launch the Python interpreter. Can be used to set environment variables for Python generators. |
+| `Halide_NO_DEFAULT_FLAGS` | Off by default. When enabled, suppresses recommended compiler flags that would be added by `add_halide_generator`                               |
 
 
 ### Imported targets
diff --git a/cmake/FindHalide_LLVM.cmake b/cmake/FindHalide_LLVM.cmake
new file mode 100644
index 000000000000..d403f91b7ccd
--- /dev/null
+++ b/cmake/FindHalide_LLVM.cmake
@@ -0,0 +1,150 @@
+# This file wraps the upstream package config modules for LLVM, Clang, and LLD
+# to fix pathological issues in their implementations. It creates imported targets
+# that wrap the key features needed by Halide.
+
+set(REASON_FAILURE_MESSAGE "")
+
+# Fallback configurations for weirdly built LLVMs
+set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL MinSizeRel Release RelWithDebInfo "")
+set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release MinSizeRel "")
+set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE Release MinSizeRel RelWithDebInfo "")
+
+find_package(LLVM ${PACKAGE_FIND_VERSION} CONFIG)
+
+set(Halide_LLVM_VERSION "${LLVM_PACKAGE_VERSION}")
+
+# TODO: deprecated in Halide 19.0.0, remove in Halide 20.0.0
+if (NOT DEFINED Halide_LLVM_SHARED_LIBS AND DEFINED Halide_SHARED_LLVM)
+    set(Halide_LLVM_SHARED_LIBS "${Halide_SHARED_LLVM}")
+    message(DEPRECATION
+            "Halide_SHARED_LLVM has been renamed to Halide_LLVM_SHARED_LIBS.")
+endif ()
+
+if (NOT DEFINED Halide_LLVM_SHARED_LIBS)
+    # Normally, we don't like making decisions for our users. However,
+    # this avoids an incompatible scenario that is checked below. So
+    # if we didn't do this, the package would fail to be found and
+    # the user would have to either rebuild LLVM or flip this value.
+    if (LLVM_FOUND AND "WebAssembly" IN_LIST LLVM_TARGETS_TO_BUILD AND LLVM_LINK_LLVM_DYLIB)
+        set(Halide_LLVM_SHARED_LIBS YES)
+    else ()
+        set(Halide_LLVM_SHARED_LIBS NO)
+    endif ()
+endif ()
+
+option(Halide_LLVM_SHARED_LIBS "Enable to link to shared libLLVM" "${Halide_LLVM_SHARED_LIBS}")
+
+if (LLVM_FOUND)
+    find_package(Clang HINTS "${LLVM_INSTALL_PREFIX}" "${LLVM_DIR}/../clang" "${LLVM_DIR}/../lib/cmake/clang")
+
+    foreach (comp IN LISTS LLVM_TARGETS_TO_BUILD)
+        if (comp STREQUAL "WebAssembly")
+            set(Halide_LLVM_${comp}_FOUND 0)
+
+            find_package(LLD HINTS "${LLVM_INSTALL_PREFIX}" "${LLVM_DIR}/../lld" "${LLVM_DIR}/../lib/cmake/lld")
+            if (NOT LLD_FOUND)
+                string(APPEND REASON_FAILURE_MESSAGE
+                       "WebAssembly was not found because liblld is missing. Did you install liblld-dev?\n")
+                continue()
+            endif ()
+
+            # LLVM has a mis-feature that allows it to build and export both static and shared libraries at the same
+            # time, while inconsistently linking its own static libraries (for lldWasm and others) to the shared
+            # library. Ignoring this causes Halide to link to both the static AND the shared LLVM libs and it breaks at
+            # runtime. See: https://github.com/halide/Halide/issues/5471
+            if (LLVM_LINK_LLVM_DYLIB AND NOT Halide_LLVM_SHARED_LIBS)
+                string(APPEND REASON_FAILURE_MESSAGE
+                       "WebAssembly was not found because LLD required by was linked to shared LLVM "
+                       "(LLVM_LINK_LLVM_DYLIB=${LLVM_LINK_LLVM_DYLIB}) but static LLVM was requested "
+                       "(Halide_LLVM_SHARED_LIBS=${Halide_LLVM_SHARED_LIBS}).\n")
+                continue()
+            endif ()
+        endif ()
+
+        set(Halide_LLVM_${comp}_FOUND 1)
+    endforeach ()
+
+    set(Halide_LLVM_SHARED_LIBRARY "LLVM")
+    if (Halide_LLVM_SHARED_LIBS AND NOT TARGET "${Halide_LLVM_SHARED_LIBRARY}")
+        string(APPEND Halide_LLVM_SHARED_LIBRARY "-NOTFOUND")
+        string(APPEND REASON_FAILURE_MESSAGE
+               "Halide_LLVM_SHARED_LIBS=${Halide_LLVM_SHARED_LIBS} but the shared LLVM target does not exist.\n")
+    endif ()
+endif ()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(
+    Halide_LLVM
+    REQUIRED_VARS LLVM_CONFIG Clang_CONFIG Halide_LLVM_SHARED_LIBRARY
+    VERSION_VAR Halide_LLVM_VERSION
+    REASON_FAILURE_MESSAGE "${REASON_FAILURE_MESSAGE}"
+    HANDLE_COMPONENTS
+    HANDLE_VERSION_RANGE
+    NAME_MISMATCHED
+)
+
+function(_Halide_LLVM_link target visibility)
+    llvm_map_components_to_libnames(comps ${ARGN})
+    target_link_libraries("${target}" "${visibility}" ${comps})
+endfunction()
+
+if (Halide_LLVM_FOUND)
+    set(Halide_LLVM_COMPONENTS "")
+    foreach (comp IN LISTS Halide_LLVM_FIND_COMPONENTS)
+        if (Halide_LLVM_${comp}_FOUND)
+            list(APPEND Halide_LLVM_COMPONENTS "${comp}")
+        endif ()
+    endforeach ()
+
+    if (NOT TARGET Halide_LLVM::Core)
+        add_library(Halide_LLVM::Core INTERFACE IMPORTED)
+
+        # LLVM_DEFINITIONS is a space-separated list instead of a more typical
+        # CMake semicolon-separated list. For a long time, CMake could handle
+        # this transparently but, since LLVM 17, the flag -D_FILE_OFFSET_BITS=64
+        # appears on 32-bit Linux. The presence of the `=` here stops CMake
+        # from splitting on spaces, instead corrupting the command line by
+        # folding the other flags into the value of -D_FILE_OFFSET_BITS=64.
+        # For better or worse, since the flag also appears twice, the second
+        # `=` is folded into the value of the first and we get errors of the
+        # form:
+        #
+        #   <command-line>: error: token "=" is not valid in preprocessor expressions
+        #
+        separate_arguments(LLVM_DEFINITIONS NATIVE_COMMAND "${LLVM_DEFINITIONS}")
+        list(REMOVE_ITEM LLVM_DEFINITIONS "-D_GLIBCXX_ASSERTIONS") # work around https://reviews.llvm.org/D142279
+        list(APPEND LLVM_DEFINITIONS "LLVM_VERSION=${LLVM_VERSION_MAJOR}${LLVM_VERSION_MINOR}")
+
+        target_compile_definitions(Halide_LLVM::Core INTERFACE ${LLVM_DEFINITIONS})
+        target_include_directories(Halide_LLVM::Core INTERFACE "${LLVM_INCLUDE_DIRS}")
+
+        set_property(TARGET Halide_LLVM::Core PROPERTY INTERFACE_CXX_RTTI "${LLVM_ENABLE_RTTI}")
+        set_property(TARGET Halide_LLVM::Core APPEND PROPERTY COMPATIBLE_INTERFACE_BOOL CXX_RTTI)
+
+        if (LLVM_LIBCXX GREATER -1)
+            target_compile_options(Halide_LLVM::Core INTERFACE "$<$<LINK_LANGUAGE:CXX>:-stdlib=libc++>")
+            target_link_options(Halide_LLVM::Core INTERFACE "$<$<LINK_LANGUAGE:CXX>:-stdlib=libc++>")
+        endif ()
+
+        if (Halide_LLVM_SHARED_LIBS)
+            target_link_libraries(Halide_LLVM::Core INTERFACE LLVM ${CMAKE_DL_LIBS})
+        else ()
+            _Halide_LLVM_link(Halide_LLVM::Core INTERFACE orcjit bitwriter linker passes)
+        endif ()
+    endif ()
+
+    foreach (comp IN LISTS Halide_LLVM_COMPONENTS)
+        if (NOT TARGET Halide_LLVM::${comp})
+            add_library(Halide_LLVM::${comp} INTERFACE IMPORTED)
+            target_link_libraries(Halide_LLVM::${comp} INTERFACE Halide_LLVM::Core)
+
+            if (NOT Halide_LLVM_SHARED_LIBS)
+                _Halide_LLVM_link(Halide_LLVM::${comp} INTERFACE ${comp})
+            endif ()
+
+            if (comp STREQUAL "WebAssembly")
+                target_link_libraries(Halide_LLVM::WebAssembly INTERFACE lldWasm)
+            endif ()
+        endif ()
+    endforeach ()
+endif ()
diff --git a/dependencies/README.md b/dependencies/README.md
index 9e11b262e70c..d59dfd2dba79 100644
--- a/dependencies/README.md
+++ b/dependencies/README.md
@@ -1,8 +1,7 @@
 # Dependencies
 
-## LLVM / WASM
-
-These are Halide's wrappers for the LLVM and WASM CMake builds/packages.
+This folder contains vendored dependencies for building Halide. They do not
+form part of the API surface.
 
 ## SPIR-V
 
diff --git a/dependencies/llvm/CMakeLists.txt b/dependencies/llvm/CMakeLists.txt
deleted file mode 100644
index cc6018cfab10..000000000000
--- a/dependencies/llvm/CMakeLists.txt
+++ /dev/null
@@ -1,189 +0,0 @@
-##
-# Find LLVM and check the version.
-##
-
-include(CMakeDependentOption)
-include(BundleStatic)
-
-# Fallback configurations for weirdly built LLVMs
-set(CMAKE_MAP_IMPORTED_CONFIG_MINSIZEREL MinSizeRel Release RelWithDebInfo "")
-set(CMAKE_MAP_IMPORTED_CONFIG_RELWITHDEBINFO RelWithDebInfo Release MinSizeRel "")
-set(CMAKE_MAP_IMPORTED_CONFIG_RELEASE Release MinSizeRel RelWithDebInfo "")
-
-find_package(LLVM ${Halide_REQUIRE_LLVM_VERSION} REQUIRED)
-find_package(Clang REQUIRED CONFIG HINTS "${LLVM_DIR}/../clang" "${LLVM_DIR}/../lib/cmake/clang")
-
-set(LLVM_PACKAGE_VERSION "${LLVM_PACKAGE_VERSION}"
-    CACHE INTERNAL "LLVM version")
-
-message(STATUS "Found LLVM ${LLVM_PACKAGE_VERSION}")
-message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}")
-message(STATUS "Using ClangConfig.cmake in: ${Clang_DIR}")
-
-if (LLVM_PACKAGE_VERSION VERSION_LESS 16.0)
-    message(FATAL_ERROR "LLVM version must be 16.0 or newer")
-endif ()
-
-if (LLVM_PACKAGE_VERSION VERSION_GREATER 20.0)
-    message(WARNING "Halide is not tested on LLVM versions beyond 20.0")
-endif ()
-
-# LLVM_DEFINITIONS is a space-separated list instead of a more typical
-# CMake semicolon-separated list. For a long time, CMake could handle
-# this transparently but, since LLVM 17, the flag -D_FILE_OFFSET_BITS=64
-# appears on 32-bit Linux. The presence of the `=` here stops CMake
-# from splitting on spaces, instead corrupting the command line by
-# folding the other flags into the value of -D_FILE_OFFSET_BITS=64.
-# For better or worse, since the flag also appears twice, the second
-# `=` is folded into the value of the first and we get errors of the
-# form:
-#
-#   <command-line>: error: token "=" is not valid in preprocessor expressions
-#
-separate_arguments(LLVM_DEFINITIONS NATIVE_COMMAND "${LLVM_DEFINITIONS}")
-set(Halide_LLVM_DEFS ${LLVM_DEFINITIONS} $<BUILD_INTERFACE:LLVM_VERSION=${LLVM_VERSION_MAJOR}${LLVM_VERSION_MINOR}>)
-
-# Note, removing -D_GLIBCXX_ASSERTIONS is a workaround for https://reviews.llvm.org/D142279
-list(REMOVE_ITEM Halide_LLVM_DEFS "-D_GLIBCXX_ASSERTIONS")
-
-##
-# Options for which version of LLVM to use
-##
-
-option(Halide_SHARED_LLVM "Link against shared LLVM (ignores components)." OFF)
-cmake_dependent_option(Halide_BUNDLE_LLVM "When built as a static library, include LLVM's objects." OFF
-                       "NOT BUILD_SHARED_LIBS" OFF)
-
-##
-# Promote LLVM/Clang executable targets
-##
-
-set_target_properties(llvm-as clang PROPERTIES IMPORTED_GLOBAL TRUE)
-
-# clang-tools-extra is optional, but provides the clang-format target
-if (TARGET clang-format)
-    set_target_properties(clang-format PROPERTIES IMPORTED_GLOBAL TRUE)
-endif ()
-
-##
-# Create options for including or excluding LLVM backends.
-##
-
-set(active_components orcjit bitwriter linker passes)
-set(known_components AArch64 AMDGPU ARM Hexagon NVPTX PowerPC RISCV WebAssembly X86)
-
-foreach (comp IN LISTS known_components)
-    string(TOUPPER "TARGET_${comp}" OPTION)
-    string(TOUPPER "WITH_${comp}" DEFINE)
-
-    if (comp STREQUAL "RISCV" AND LLVM_PACKAGE_VERSION VERSION_LESS 17.0)
-        # We default the RISCV target to OFF for LLVM versions prior to 17.0;
-        # it's not clear how robust and well-tested Halide's RISCV codegen
-        # is with LLVM16, and a great deal of effort is being put into
-        # improving it in LLVM17... so default to off so that people won't
-        # hurt themselves too badly.
-        cmake_dependent_option(${OPTION} "Include ${comp} target" OFF
-                               "${comp} IN_LIST LLVM_TARGETS_TO_BUILD" OFF)
-    else ()
-        cmake_dependent_option(${OPTION} "Include ${comp} target" ON
-                               "${comp} IN_LIST LLVM_TARGETS_TO_BUILD" OFF)
-    endif ()
-    if (${OPTION} OR Halide_SHARED_LLVM)
-        message(STATUS "Enabling ${comp} backend")
-        list(APPEND Halide_LLVM_DEFS $<BUILD_INTERFACE:${DEFINE}>)
-        list(APPEND active_components ${comp})
-    else ()
-        message(STATUS "Disabling ${comp} backend")
-    endif ()
-endforeach ()
-
-set(wasm_libs "")
-if (TARGET_WEBASSEMBLY)
-    find_package(LLD CONFIG REQUIRED HINTS "${LLVM_DIR}/../lld" "${LLVM_DIR}/../lib/cmake/lld")
-    message(STATUS "Using LLDConfig.cmake in: ${LLD_DIR}")
-
-    # LLVM has a mis-feature that allows it to build and export both static and shared libraries at the same
-    # time, while inconsistently linking its own static libraries (for lldWasm and others) to the shared library.
-    # Ignoring this causes Halide to link to both the static AND the shared LLVM libs and it breaks at runtime.
-    # From issue: https://github.com/halide/Halide/issues/5471
-    if (LLVM_LINK_LLVM_DYLIB AND NOT Halide_SHARED_LLVM)
-        message(FATAL_ERROR "LLD was linked to shared LLVM (see: LLVM_LINK_LLVM_DYLIB), "
-                "but static LLVM was requested. Re-configure with Halide_SHARED_LLVM=YES "
-                "to enable WebAssembly, or disable WebAssembly with TARGET_WEBASSEMBLY=OFF.")
-    endif ()
-
-    set(wasm_libs lldWasm)
-endif ()
-
-##
-# Create Halide::LLVM library alias pointing to the correct LLVM
-# among shared, static, and bundled.
-##
-
-add_library(Halide_LLVM INTERFACE)
-add_library(Halide::LLVM ALIAS Halide_LLVM)
-
-set_target_properties(Halide_LLVM PROPERTIES EXPORT_NAME LLVM)
-target_include_directories(Halide_LLVM INTERFACE "$<BUILD_INTERFACE:${LLVM_INCLUDE_DIRS}>")
-target_compile_definitions(Halide_LLVM INTERFACE ${Halide_LLVM_DEFS})
-
-# Link LLVM libraries to Halide_LLVM, depending on shared, static, or bundled selection.
-if (Halide_SHARED_LLVM)
-    # llvm_map_components_to_libnames is not safe to call if the LLVM static libraries
-    # aren't in the package. This happens on Gentoo Linux at least, but might also happen
-    # with custom LLVM build configurations.
-    target_link_libraries(Halide_LLVM INTERFACE LLVM ${wasm_libs} ${CMAKE_DL_LIBS})
-else ()
-    llvm_map_components_to_libnames(llvm_targets ${active_components})
-    list(APPEND llvm_targets ${wasm_libs})
-    if (Halide_BUNDLE_LLVM)
-        bundle_static(Halide_LLVM LIBRARIES ${llvm_targets})
-    else ()
-        target_link_libraries(Halide_LLVM INTERFACE ${llvm_targets})
-    endif ()
-endif ()
-
-##
-# Language options interface library
-##
-
-add_library(Halide_LanguageOptions INTERFACE)
-add_library(Halide::LanguageOptions ALIAS Halide_LanguageOptions)
-
-set_target_properties(Halide_LanguageOptions PROPERTIES EXPORT_NAME LanguageOptions)
-
-option(Halide_ENABLE_RTTI "Enable RTTI" ${LLVM_ENABLE_RTTI})
-if (Halide_ENABLE_RTTI AND NOT LLVM_ENABLE_RTTI)
-    message(FATAL_ERROR "Can't enable RTTI. LLVM was compiled without it")
-endif ()
-
-if (Halide_ENABLE_RTTI)
-    message(STATUS "Compiling Halide WITH RTTI.")
-    target_compile_definitions(Halide_LanguageOptions INTERFACE HALIDE_ENABLE_RTTI)
-else ()
-    message(STATUS "Compiling Halide WITHOUT RTTI.")
-    target_compile_options(Halide_LanguageOptions INTERFACE
-                           $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:/GR->
-                           $<$<COMPILE_LANG_AND_ID:CXX,GNU,Clang,AppleClang>:-fno-rtti>)
-endif ()
-
-option(Halide_ENABLE_EXCEPTIONS "Enable exceptions" ON)
-if (Halide_ENABLE_EXCEPTIONS)
-    message(STATUS "Compiling Halide WITH exceptions.")
-    target_compile_definitions(Halide_LanguageOptions INTERFACE HALIDE_WITH_EXCEPTIONS)
-else ()
-    message(STATUS "Compiling Halide WITHOUT exceptions.")
-    target_compile_options(Halide_LanguageOptions INTERFACE
-                           $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:/EHs-c->
-                           $<$<COMPILE_LANG_AND_ID:CXX,GNU,Clang,AppleClang>:-fno-exceptions>)
-    target_compile_definitions(Halide_LanguageOptions INTERFACE
-                               $<$<COMPILE_LANG_AND_ID:CXX,MSVC>:_HAS_EXCEPTIONS=0>)
-endif ()
-
-if (LLVM_LIBCXX GREATER -1)
-    message(STATUS "LLVM linked to libc++. Adding to interface requirements.")
-    target_compile_options(Halide_LanguageOptions INTERFACE
-                           $<$<STREQUAL:$<TARGET_PROPERTY:LINKER_LANGUAGE>,CXX>:-stdlib=libc++>)
-    target_link_options(Halide_LanguageOptions INTERFACE
-                        $<$<STREQUAL:$<TARGET_PROPERTY:LINKER_LANGUAGE>,CXX>:-stdlib=libc++>)
-endif ()
diff --git a/packaging/CMakeLists.txt b/packaging/CMakeLists.txt
index 5d1239384120..675c52b19983 100644
--- a/packaging/CMakeLists.txt
+++ b/packaging/CMakeLists.txt
@@ -21,7 +21,7 @@ set(Halide_INSTALL_TOOLSDIR "${CMAKE_INSTALL_DATADIR}/tools"
 # Main library exports
 ##
 
-install(TARGETS Halide Halide_Generator Halide_GenGen Halide_LanguageOptions
+install(TARGETS Halide Halide_Generator Halide_GenGen
         EXPORT Halide_Targets
         RUNTIME COMPONENT Halide_Runtime
         LIBRARY COMPONENT Halide_Runtime
@@ -36,13 +36,6 @@ if (TARGET Halide_Adams2019)
             NAMELINK_COMPONENT Halide_Development)
 endif ()
 
-# Halide_LLVM
-foreach (dep IN ITEMS Halide_LLVM)
-    if (TARGET ${dep})
-        install(TARGETS ${dep} EXPORT Halide_Targets)
-    endif ()
-endforeach ()
-
 ##
 # Runtime headers
 ##
@@ -52,7 +45,7 @@ install(TARGETS Halide_Runtime
         FILE_SET HEADERS COMPONENT Halide_Development)
 
 ##
-# Library-type-agnostic interface targets
+# Halide tools
 ##
 
 target_sources(Halide_RunGenMain INTERFACE $<INSTALL_INTERFACE:${Halide_INSTALL_TOOLSDIR}/RunGenMain.cpp>)
@@ -151,15 +144,19 @@ endif ()
 # If Halide explicitly links against shared LLVM or if it is a static library
 # and we are not bundling our static dependencies, then end-users must have
 # the relevant system libraries installed.
-if (Halide_SHARED_LLVM OR (NOT BUILD_SHARED_LIBS AND NOT Halide_BUNDLE_LLVM))
+if (Halide_LLVM_SHARED_LIBS OR (NOT BUILD_SHARED_LIBS AND NOT Halide_BUNDLE_LLVM))
     set(depFile "${CMAKE_CURRENT_BINARY_DIR}/Halide-${type}-deps.cmake")
-    file(WRITE "${depFile}" "find_dependency(LLVM ${LLVM_PACKAGE_VERSION})\n")
-
-    if (TARGET_WEBASSEMBLY)
-        file(APPEND "${depFile}" [[find_dependency(LLD HINTS "${LLVM_DIR}/../lld" "${LLVM_DIR}/../lib/cmake/lld")]])
-    endif ()
-
-    install(FILES "${depFile}"
+    file(CONFIGURE
+         OUTPUT "${depFile}"
+         CONTENT [[
+set(Halide_LLVM_SHARED_LIBS @Halide_LLVM_SHARED_LIBS@)
+find_dependency(
+    Halide_LLVM @Halide_LLVM_VERSION@
+    COMPONENTS @Halide_LLVM_COMPONENTS@
+)
+]] @ONLY)
+
+    install(FILES "${depFile}" "${Halide_SOURCE_DIR}/cmake/FindHalide_LLVM.cmake"
             DESTINATION ${Halide_INSTALL_CMAKEDIR}
             COMPONENT Halide_Development)
 endif ()
diff --git a/python_bindings/apps/CMakeLists.txt b/python_bindings/apps/CMakeLists.txt
index 1709ea434e9e..0e5599c998a2 100644
--- a/python_bindings/apps/CMakeLists.txt
+++ b/python_bindings/apps/CMakeLists.txt
@@ -1,9 +1,9 @@
-if (TARGET_WEBASSEMBLY AND Halide_TARGET MATCHES "wasm")
+if (Halide_TARGET MATCHES "wasm")
     message(WARNING "Python apps are skipped under WASM.")
     return()
 endif ()
 
-if (TARGET_VULKAN AND Halide_TARGET MATCHES "vulkan")
+if (Halide_TARGET MATCHES "vulkan")
     message(WARNING "Python apps are skipped under Vulkan.")
     return()
 endif ()
diff --git a/python_bindings/test/CMakeLists.txt b/python_bindings/test/CMakeLists.txt
index f25330797c9f..d957d9fb5c2b 100644
--- a/python_bindings/test/CMakeLists.txt
+++ b/python_bindings/test/CMakeLists.txt
@@ -1,4 +1,4 @@
-if (TARGET_WEBASSEMBLY AND Halide_TARGET MATCHES "wasm")
+if (Halide_TARGET MATCHES "wasm")
     message(WARNING "Python tests are skipped under WASM.")
     return()
 endif ()
diff --git a/python_bindings/test/correctness/CMakeLists.txt b/python_bindings/test/correctness/CMakeLists.txt
index 74acaed0deea..46dfae3dda60 100644
--- a/python_bindings/test/correctness/CMakeLists.txt
+++ b/python_bindings/test/correctness/CMakeLists.txt
@@ -1,4 +1,4 @@
-if (TARGET_WEBASSEMBLY AND Halide_TARGET MATCHES "webgpu")
+if (Halide_TARGET MATCHES "webgpu")
     message(WARNING "Python correctness tests are not supported with WebGPU.")
     return()
 endif ()
diff --git a/python_bindings/tutorial/CMakeLists.txt b/python_bindings/tutorial/CMakeLists.txt
index 1f9fa17f3e0f..9b17c7516f77 100644
--- a/python_bindings/tutorial/CMakeLists.txt
+++ b/python_bindings/tutorial/CMakeLists.txt
@@ -1,4 +1,4 @@
-if (TARGET_WEBASSEMBLY AND Halide_TARGET MATCHES "webgpu")
+if (Halide_TARGET MATCHES "webgpu")
     message(WARNING "Python tutorials are not supported with WebGPU.")
     return()
 endif ()
@@ -24,12 +24,12 @@ set(tests
 set(PYPATH_lesson_10_aot_compilation_run "$<TARGET_FILE_DIR:lesson_10_halide>")
 
 foreach (test IN LISTS tests)
-    if (TARGET_WEBASSEMBLY AND Halide_TARGET MATCHES "wasm" AND test MATCHES "lesson_10")
+    if (Halide_TARGET MATCHES "wasm" AND test MATCHES "lesson_10")
         message(WARNING "Not all tutorials build under WASM.")
         continue()
     endif ()
 
-    if (TARGET_VULKAN AND Halide_TARGET MATCHES "vulkan" AND test MATCHES "lesson_10")
+    if (Halide_TARGET MATCHES "vulkan" AND test MATCHES "lesson_10")
         message(WARNING "Not all tutorials build under Vulkan.")
         continue()
     endif ()
@@ -42,9 +42,9 @@ foreach (test IN LISTS tests)
     )
 endforeach ()
 
-if (TARGET_WEBASSEMBLY AND Halide_TARGET MATCHES "wasm")
+if (Halide_TARGET MATCHES "wasm")
     message(WARNING "Not all tutorials build under WASM.")
-elseif (TARGET_VULKAN AND Halide_TARGET MATCHES "vulkan")
+elseif (Halide_TARGET MATCHES "vulkan")
     message(WARNING "Not all tutorials build under Vulkan.")
 else ()
     ## Add some hacks for getting CMake to delay compiling lesson_10_halide until after the test has run. The "better" way
diff --git a/run-clang-tidy.sh b/run-clang-tidy.sh
index 1b4fc808a0a9..baf896d3f78e 100755
--- a/run-clang-tidy.sh
+++ b/run-clang-tidy.sh
@@ -55,12 +55,12 @@ fi
 CLANG_TIDY_BUILD_DIR=`mktemp -d`
 echo CLANG_TIDY_BUILD_DIR = ${CLANG_TIDY_BUILD_DIR}
 
-# Specify Halide_SHARED_LLVM=ON because some installers may provide only that.
+# Specify Halide_LLVM_SHARED_LIBS=ON because some installers may provide only that.
 echo Building compile_commands.json...
 cmake -DCMAKE_BUILD_TYPE=Debug \
       -DCMAKE_EXPORT_COMPILE_COMMANDS=ON \
       -DHalide_CLANG_TIDY_BUILD=ON \
-      -DHalide_SHARED_LLVM=ON \
+      -DHalide_LLVM_SHARED_LIBS=ON \
       -DLLVM_DIR=${CLANG_TIDY_LLVM_INSTALL_DIR}/lib/cmake/llvm \
       -S ${ROOT_DIR} \
       -B ${CLANG_TIDY_BUILD_DIR} \
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 78b137239272..3c481a60e2aa 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -5,6 +5,16 @@
 add_library(Halide)
 add_library(Halide::Halide ALIAS Halide)
 
+##
+# CodeGen backends
+
+# LLVM
+foreach (backend IN LISTS Halide_LLVM_COMPONENTS)
+    string(TOUPPER "WITH_${backend}" definition)
+    target_compile_definitions(Halide PRIVATE "${definition}")
+    target_link_libraries(Halide PRIVATE Halide_LLVM::${backend})
+endforeach ()
+
 ##
 # Lists of source files. Keep ALL lists sorted in alphabetical order.
 ##
@@ -506,8 +516,6 @@ if (WITH_SERIALIZATION_JIT_ROUNDTRIP_TESTING)
     target_compile_definitions(Halide PRIVATE WITH_SERIALIZATION_JIT_ROUNDTRIP_TESTING)
 endif ()
 
-target_link_libraries(Halide PRIVATE Halide::LLVM)
-target_link_libraries(Halide PUBLIC Halide::LanguageOptions)
 target_compile_features(Halide PUBLIC cxx_std_17)
 if (NOT BUILD_SHARED_LIBS)
     target_compile_definitions(Halide PRIVATE Halide_STATIC_DEFINE)
@@ -543,47 +551,45 @@ target_compile_definitions(Halide PUBLIC
 # WasmExecutor backend selection
 ##
 
-if (TARGET_WEBASSEMBLY)
-    set(Halide_WASM_BACKEND "wabt"
-        CACHE STRING "Which backend to use for Halide's WASM testing.")
-    set_property(CACHE Halide_WASM_BACKEND PROPERTY STRINGS "wabt;V8;OFF")
-
-    if (WITH_WABT AND NOT WITH_V8)
-        message(DEPRECATION "WITH_WABT has been replaced by Halide_WASM_BACKEND=\"wabt\"")
-        set(Halide_WASM_BACKEND "wabt")
-    elseif (NOT WITH_WABT AND WITH_V8)
-        message(DEPRECATION "WITH_V8 has been replaced by Halide_WASM_BACKEND=\"V8\"")
-        set(Halide_WASM_BACKEND "V8")
-    elseif (WITH_WABT AND WITH_V8)
-        message(FATAL_ERROR "Cannot use both WABT and V8 at the same time, disable one of them.")
-    elseif (DEFINED WITH_WABT AND DEFINED WITH_V8 AND NOT WITH_WABT AND NOT WITH_V8)
-        message(DEPRECATION "Disabling both WITH_WABT and WITH_V8 has been replaced by Halide_WASM_BACKEND=\"OFF\"")
-        set(Halide_WASM_BACKEND "OFF")
-    endif ()
+set(Halide_WASM_BACKEND "wabt"
+    CACHE STRING "Which backend to use for Halide's WASM testing.")
+set_property(CACHE Halide_WASM_BACKEND PROPERTY STRINGS "wabt;V8;OFF")
+
+if (WITH_WABT AND NOT WITH_V8)
+    message(DEPRECATION "WITH_WABT has been replaced by Halide_WASM_BACKEND=\"wabt\"")
+    set(Halide_WASM_BACKEND "wabt")
+elseif (NOT WITH_WABT AND WITH_V8)
+    message(DEPRECATION "WITH_V8 has been replaced by Halide_WASM_BACKEND=\"V8\"")
+    set(Halide_WASM_BACKEND "V8")
+elseif (WITH_WABT AND WITH_V8)
+    message(FATAL_ERROR "Cannot use both WABT and V8 at the same time, disable one of them.")
+elseif (DEFINED WITH_WABT AND DEFINED WITH_V8 AND NOT WITH_WABT AND NOT WITH_V8)
+    message(DEPRECATION "Disabling both WITH_WABT and WITH_V8 has been replaced by Halide_WASM_BACKEND=\"OFF\"")
+    set(Halide_WASM_BACKEND "OFF")
+endif ()
 
-    if (MSVC AND Halide_WASM_BACKEND STREQUAL "wabt")
-        message(WARNING "wabt is not yet supported on Windows")
-        set(Halide_WASM_BACKEND "OFF")
-    endif ()
+if (MSVC AND Halide_WASM_BACKEND STREQUAL "wabt")
+    message(WARNING "wabt is not yet supported on Windows")
+    set(Halide_WASM_BACKEND "OFF")
+endif ()
+
+if (Halide_WASM_BACKEND STREQUAL "wabt")
+    find_package(wabt 1.0.36 REQUIRED)
 
-    if (Halide_WASM_BACKEND STREQUAL "wabt")
-        find_package(wabt 1.0.36 REQUIRED)
-
-        if (Halide_USE_FETCHCONTENT AND NOT BUILD_SHARED_LIBS)
-            target_sources(Halide PRIVATE "$<TARGET_OBJECTS:wabt::wabt>")
-            target_link_libraries(Halide PRIVATE "$<BUILD_LOCAL_INTERFACE:$<COMPILE_ONLY:wabt::wabt>>")
-        else ()
-            target_link_libraries(Halide PRIVATE wabt::wabt)
-        endif ()
-
-        target_compile_definitions(Halide PRIVATE WITH_WABT)
-    elseif (Halide_WASM_BACKEND STREQUAL "V8")
-        find_package(V8 REQUIRED)
-        target_link_libraries(Halide PRIVATE V8::V8)
-        target_compile_definitions(Halide PRIVATE WITH_V8)
-    elseif (Halide_WASM_BACKEND)
-        message(FATAL_ERROR "Unknown Halide_WASM_BACKEND `${Halide_WASM_BACKEND}`")
+    if (Halide_USE_FETCHCONTENT AND NOT BUILD_SHARED_LIBS)
+        target_sources(Halide PRIVATE "$<TARGET_OBJECTS:wabt::wabt>")
+        target_link_libraries(Halide PRIVATE "$<BUILD_LOCAL_INTERFACE:$<COMPILE_ONLY:wabt::wabt>>")
+    else ()
+        target_link_libraries(Halide PRIVATE wabt::wabt)
     endif ()
+
+    target_compile_definitions(Halide PRIVATE WITH_WABT)
+elseif (Halide_WASM_BACKEND STREQUAL "V8")
+    find_package(V8 REQUIRED)
+    target_link_libraries(Halide PRIVATE V8::V8)
+    target_compile_definitions(Halide PRIVATE WITH_V8)
+elseif (Halide_WASM_BACKEND)
+    message(FATAL_ERROR "Unknown Halide_WASM_BACKEND `${Halide_WASM_BACKEND}`")
 endif ()
 
 ##
@@ -609,42 +615,54 @@ target_compile_definitions(Halide
 )
 
 ##
-# Set up additional backend options for Halide
-##
+# RTTI and exceptions settings
 
-option(TARGET_OPENCL "Include OpenCL-C target" ON)
-if (TARGET_OPENCL)
-    target_compile_definitions(Halide PRIVATE WITH_OPENCL)
-endif ()
+option(Halide_ENABLE_RTTI "Enable RTTI in Halide" "${LLVM_ENABLE_RTTI}")
+set_property(TARGET Halide PROPERTY CXX_RTTI "${Halide_ENABLE_RTTI}")
+set_property(TARGET Halide APPEND PROPERTY COMPATIBLE_INTERFACE_BOOL CXX_RTTI)
 
-option(TARGET_METAL "Include Metal target" ON)
-if (TARGET_METAL)
-    target_compile_definitions(Halide PRIVATE WITH_METAL)
+if (Halide_ENABLE_RTTI)
+    target_compile_definitions(Halide PUBLIC HALIDE_ENABLE_RTTI)
+else ()
+    target_compile_options(
+        Halide
+        PUBLIC
+        "$<$<COMPILE_LANG_AND_ID:CXX,MSVC>:/GR->"
+        "$<$<COMPILE_LANG_AND_ID:CXX,GNU,Clang,AppleClang>:-fno-rtti>"
+    )
 endif ()
 
-option(TARGET_D3D12COMPUTE "Include Direct3D 12 Compute target" ON)
-if (TARGET_D3D12COMPUTE)
-    target_compile_definitions(Halide PRIVATE WITH_D3D12)
+option(Halide_ENABLE_EXCEPTIONS "Enable exceptions in Halide" ON)
+if (Halide_ENABLE_EXCEPTIONS)
+    target_compile_definitions(Halide PUBLIC HALIDE_WITH_EXCEPTIONS)
+else ()
+    target_compile_options(
+        Halide
+        PUBLIC
+        "$<$<COMPILE_LANG_AND_ID:CXX,MSVC>:/EHs-c->"
+        "$<$<COMPILE_LANG_AND_ID:CXX,GNU,Clang,AppleClang>:-fno-exceptions>"
+    )
+    target_compile_definitions(
+        Halide PUBLIC "$<$<COMPILE_LANG_AND_ID:CXX,MSVC>:_HAS_EXCEPTIONS=0>"
+    )
 endif ()
 
-if (TARGET_VULKAN)
-    message(STATUS "Enabling Vulkan target")
-    target_compile_definitions(Halide PRIVATE WITH_VULKAN)
-endif()
+##
+# GPU CodeGen backends
 
-if (TARGET_SPIRV)
-    find_package(
-        SPIRV-Headers 1.5.5 REQUIRED
-        HINTS "${Halide_SOURCE_DIR}/dependencies/spirv"
-    )
-    target_compile_definitions(Halide PRIVATE WITH_SPIRV)
-    target_link_libraries(Halide PRIVATE "$<BUILD_LOCAL_INTERFACE:SPIRV-Headers::SPIRV-Headers>")
-endif ()
+find_package(
+    SPIRV-Headers 1.5.5 REQUIRED
+    HINTS "${Halide_SOURCE_DIR}/dependencies/spirv"
+)
+
+target_link_libraries(Halide PRIVATE "$<BUILD_LOCAL_INTERFACE:SPIRV-Headers::SPIRV-Headers>")
 
-option(TARGET_WEBGPU "Include WebGPU target" ON)
-if (TARGET_WEBGPU)
-    target_compile_definitions(Halide PRIVATE WITH_WEBGPU)
-endif()
+target_compile_definitions(Halide PRIVATE WITH_D3D12)
+target_compile_definitions(Halide PRIVATE WITH_METAL)
+target_compile_definitions(Halide PRIVATE WITH_OPENCL)
+target_compile_definitions(Halide PRIVATE WITH_SPIRV)
+target_compile_definitions(Halide PRIVATE WITH_VULKAN)
+target_compile_definitions(Halide PRIVATE WITH_WEBGPU)
 
 ##
 # Add autoschedulers to the build.
diff --git a/src/runtime/CMakeLists.txt b/src/runtime/CMakeLists.txt
index 061b6331fcce..3f5f876b1e37 100644
--- a/src/runtime/CMakeLists.txt
+++ b/src/runtime/CMakeLists.txt
@@ -203,9 +203,9 @@ foreach (i IN LISTS RUNTIME_CPP)
                 endif ()
             # Windows on ARM
             elseif (i MATCHES "windows_.*_arm$")
-                if (j EQUAL 32 AND TARGET_ARM)
+                if (j EQUAL 32 AND "ARM" IN_LIST Halide_LLVM_COMPONENTS)
                     set(TARGET "arm-unknown-windows-unknown")
-                elseif (j EQUAL 64 AND TARGET_AARCH64)
+                elseif (j EQUAL 64 AND "AArch64" IN_LIST Halide_LLVM_COMPONENTS)
                     set(TARGET "aarch64-unknown-windows-unknown")
                 else ()
                     continue()
diff --git a/test/autoschedulers/CMakeLists.txt b/test/autoschedulers/CMakeLists.txt
index 38dd5a65a176..c79e6d040899 100644
--- a/test/autoschedulers/CMakeLists.txt
+++ b/test/autoschedulers/CMakeLists.txt
@@ -5,7 +5,7 @@
 # autoschedulers_cuda: this autoscheduler is expected to work on Cuda targets (but probably not other GPU targets)
 
 add_subdirectory(adams2019)
-if (TARGET_NVPTX)
+if ("NVPTX" IN_LIST Halide_LLVM_COMPONENTS)
   add_subdirectory(anderson2021)
 else()
   message(STATUS "Not testing anderson2021 because PTX is not enabled for this build of Halide")
diff --git a/test/autoschedulers/li2018/CMakeLists.txt b/test/autoschedulers/li2018/CMakeLists.txt
index 2260a87dfd3d..ee49c585e8c1 100644
--- a/test/autoschedulers/li2018/CMakeLists.txt
+++ b/test/autoschedulers/li2018/CMakeLists.txt
@@ -28,7 +28,7 @@ tests(GROUPS li2018 autoschedulers_cpu autoschedulers_gpu
 add_dependencies(li2018_test Halide::Li2018)
 
 if (WITH_PYTHON_BINDINGS)
-    if (TARGET_WEBASSEMBLY AND Halide_TARGET MATCHES "webgpu")
+    if (Halide_TARGET MATCHES "webgpu")
         message(WARNING "li2018_gradient_autoscheduler_test_py is not supported with WebGPU.")
     else()
         find_package(Python3 REQUIRED COMPONENTS Interpreter Development.Module)
diff --git a/test/generator/CMakeLists.txt b/test/generator/CMakeLists.txt
index 3e1a656cefcf..be1ccdd47527 100644
--- a/test/generator/CMakeLists.txt
+++ b/test/generator/CMakeLists.txt
@@ -6,7 +6,7 @@
 # Some tests are not available when compiling for WASM.
 ##
 
-if (TARGET_WEBASSEMBLY AND Halide_TARGET MATCHES "wasm")
+if (Halide_TARGET MATCHES "wasm")
     set(_USING_WASM 1)
 else()
     set(_USING_WASM 0)
@@ -232,11 +232,13 @@ function(_add_one_aot_test TARGET)
             target_compile_definitions("${TARGET}" PRIVATE WITH_DAWN_NATIVE)
         endif ()
     endif ()
-    if (TARGET_NVPTX AND Halide_TARGET MATCHES "cuda")
-        target_link_libraries("${TARGET}" PRIVATE CUDA::cuda_driver CUDA::cudart)
-    endif ()
-    if (TARGET_NVPTX AND Halide_TARGET MATCHES "opencl")
-        target_link_libraries("${TARGET}" PRIVATE OpenCL::OpenCL)
+    if ("NVPTX" IN_LIST Halide_LLVM_COMPONENTS)
+        if (Halide_TARGET MATCHES "cuda")
+            target_link_libraries("${TARGET}" PRIVATE CUDA::cuda_driver CUDA::cudart)
+        endif ()
+        if (Halide_TARGET MATCHES "opencl")
+            target_link_libraries("${TARGET}" PRIVATE OpenCL::OpenCL)
+        endif ()
     endif ()
     add_halide_test("${TARGET}" GROUPS generator ${args_GROUPS})
 endfunction()
@@ -307,11 +309,13 @@ function(_add_halide_aot_tests NAME)
     endif()
 endfunction()
 
-if (TARGET_NVPTX AND Halide_TARGET MATCHES "cuda")
-    find_package(CUDAToolkit REQUIRED)
-endif ()
-if (TARGET_NVPTX AND Halide_TARGET MATCHES "opencl")
-    find_package(OpenCL REQUIRED)
+if ("NVPTX" IN_LIST Halide_LLVM_COMPONENTS)
+    if (Halide_TARGET MATCHES "cuda")
+        find_package(CUDAToolkit REQUIRED)
+    endif ()
+    if (Halide_TARGET MATCHES "opencl")
+        find_package(OpenCL REQUIRED)
+    endif ()
 endif ()
 
 ##
@@ -425,7 +429,7 @@ if (NOT ${_USING_WASM})
                           FEATURES c_plus_plus_name_mangling
                           EXTERNS cxx_mangling_externs)
     _add_halide_aot_tests(cxx_mangling)
-    if (TARGET_NVPTX AND Halide_TARGET MATCHES "cuda")
+    if ("NVPTX" IN_LIST Halide_LLVM_COMPONENTS AND Halide_TARGET MATCHES "cuda")
         add_halide_library(cxx_mangling_gpu
                            FROM cxx_mangling.generator
                            GENERATOR cxx_mangling
diff --git a/tutorial/CMakeLists.txt b/tutorial/CMakeLists.txt
index ee81fcb7a545..6e872e2153d4 100644
--- a/tutorial/CMakeLists.txt
+++ b/tutorial/CMakeLists.txt
@@ -49,8 +49,8 @@ add_tutorial(lesson_07_multi_stage_pipelines.cpp WITH_IMAGE_IO)
 add_tutorial(lesson_08_scheduling_2.cpp WITH_IMAGE_IO WITH_OPENMP GROUPS multithreaded)
 add_tutorial(lesson_09_update_definitions.cpp WITH_IMAGE_IO WITH_OPENMP GROUPS multithreaded)
 
-if (TARGET_NVPTX)
-    if (TARGET_WEBASSEMBLY AND Halide_TARGET MATCHES "wasm")
+if ("NVPTX" IN_LIST Halide_LLVM_COMPONENTS)
+    if (Halide_TARGET MATCHES "wasm")
         # TODO: Requires custom build rules to work under wasm.
         message(WARNING "Not all tutorials build under WASM.")
     else ()
@@ -154,7 +154,7 @@ set_tests_properties(tutorial_lesson_15_check_files PROPERTIES
                      FIXTURES_REQUIRED tutorial_lesson_15)
 
 # Lesson 16
-if (TARGET_WEBASSEMBLY AND Halide_TARGET MATCHES "wasm")
+if (Halide_TARGET MATCHES "wasm")
     # TODO: Requires custom build rules to work under wasm
     message(WARNING "Not all tutorials build under WASM.")
 else ()
