Test the bring-your-own-LLVM path. (#14035)

* Adds a`byo_llvm` CI job. Do we want to disable running it on PRs? It's
enabled for now so we get live coverage while iterating on this PR but
we can always flip that.
* Note: the current CI job was the first to have ccache enabled so it
didn't benefit from ccache hits; subsequent ones should be cheaper (this
initial one took 15 minutes).
* Changes to the (user-facing) `byo_llvm.sh` script:
* Canonicalize paths to absolute paths. These end up in CMake variables
like `CMAKE_MODULE_PATH` where relative paths are a footgun.
* Defaults to buiding the `llvm-cpu` target backend. Users can still
disable it, but at the moment about 20 tests fail without it enabled
(filed #14034). Not a problem for users unless they run IREE tests.
Until that's fixed, enabling that here allows to keep the CI job aligned
with the default, and increases coverage (otherwise, users who do enable
`llvm-cpu` would be outside of CI coverage).
* Changes both the name and the default values of the environment
variables controlling build/install dirs. **Caveat existing users, this
could break incremental rebuilds -- as this is moving to a new
directory, it will lose existing values of CMake variables, etc.**
* `LLVM_BUILD_DIR` changed to `IREE_BYOLLVM_BUILD_DIR` because that's
only the parent directory under which we create `llvm/`, `mlir/`,
`iree/` subdirs. The former name made it sound like it was the `llvm/`
build dir itself.
* Default value `iree-llvm-build` likewise changed to
`iree-byollvm-build`.
* Changes to general CMake code:
* Continuing along the lines of earlier `byo_llvm` fixes (#13968) which
introduced `IREE_CLANG_BINARY` to refer to the `clang` binary regardless
of whether it's a CMake target (which is only the case in the bundled
build, not in `byo_llvm`), running tests revealed that we needed the
same for `lld`, so here comes `IREE_LLD_BINARY`, so we can execute `lld`
even if we don't have a `IREE_LLD_TARGET`.
* The one part of the code that decidedly doesn't currently work with
`byo_llvm` is the linking part of the Compiler API, because it relies on
linking against LLD libraries, and for that it currently requires
IREE_LLD_TARGET. I don't know how easy or feasible that would be to
overcome (though that might be easy if those LLD libraries are exported
by the LLVM install).
* Changes to tests:
* `assign_target_devices.mlir` was assuming the `vulkan-spirv` backend
to be enabled.
* A number of tests, mainly tests that depend on the Compiler API, are
skipped when `IREE_LLD_TARGET` is not defined.

Fixes #13989
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 43171e2..2d30ccd 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -645,6 +645,31 @@
             ./build_tools/cmake/build_all.sh \
             "${BUILD_DIR}"
 
+  byo_llvm:
+    needs: setup
+    if: fromJson(needs.setup.outputs.should-run)
+    runs-on:
+      - self-hosted # must come first
+      - runner-group=${{ needs.setup.outputs.runner-group }}
+      - environment=${{ needs.setup.outputs.runner-env }}
+      - cpu
+      - os-family=Linux
+    steps:
+      - name: "Checking out repository"
+        uses: actions/checkout@ac593985615ec2ede58e132d2e21d2b1cbd6127c # v3.3.0
+        with:
+          submodules: true
+      - name: "Building and testing with bring-your-own-LLVM"
+        env:
+          IREE_WRITE_REMOTE_CCACHE: ${{ needs.setup.outputs.write-caches }}
+        run: |
+          ./build_tools/github_actions/docker_run.sh \
+            --env "IREE_CCACHE_GCP_TOKEN=$(gcloud auth application-default print-access-token)" \
+            --env "IREE_WRITE_REMOTE_CCACHE=${IREE_WRITE_REMOTE_CCACHE}" \
+            --env "CCACHE_NAMESPACE=gcr.io/iree-oss/base@sha256:7dbb7e97e0baa6d4512822b5cd4f601d840a6f950f67c2df497a24cae64a0595" \
+            gcr.io/iree-oss/base@sha256:7dbb7e97e0baa6d4512822b5cd4f601d840a6f950f67c2df497a24cae64a0595 \
+            ./build_tools/cmake/build_and_test_byo_llvm.sh
+
   ############################### Configurations ###############################
   # Jobs that build and run IREE e2e tests/benchmarks                          #
   ##############################################################################
@@ -997,6 +1022,7 @@
       - gcc
       - tracing
       - debug
+      - byo_llvm
 
       # Crosscompilation
       - cross_compile_and_test
diff --git a/build_tools/cmake/build_and_test_byo_llvm.sh b/build_tools/cmake/build_and_test_byo_llvm.sh
new file mode 100755
index 0000000..a2120ac
--- /dev/null
+++ b/build_tools/cmake/build_and_test_byo_llvm.sh
@@ -0,0 +1,35 @@
+#!/bin/bash
+
+# Copyright 2023 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
+
+# Build and test, using CMake/CTest, with the bring-your-own-LLVM path.
+
+set -xeuo pipefail
+
+source build_tools/cmake/setup_ccache.sh
+
+# Environment variables read by byo_llvm.sh.
+export IREE_BYOLLVM_BUILD_DIR="${1:-build-byo-llvm}"
+export IREE_BYOLLVM_INSTALL_DIR="${IREE_BYOLLVM_BUILD_DIR}/install"
+
+# Note: by using the `build_llvm` action here, we are exercising byo_llvm.sh's
+# ability to build LLVM... from our own third_party/llvm-project. That's not
+# the most intuitive interpretation of "bring your own LLVM", since as far as
+# the source repository is concerned, that's just the standard IREE-bundled one,
+# but that exists for good reasons as some users need control more over the
+# building and packaging than over the source repository, and that's good to
+# have test coverage for, and of course that's more convenient for us to test.
+build_tools/scripts/byo_llvm.sh build_llvm
+
+build_tools/scripts/byo_llvm.sh build_mlir
+build_tools/scripts/byo_llvm.sh build_iree
+
+echo "*********************** TESTING IREE **********************************"
+iree_build_dir="${IREE_BYOLLVM_BUILD_DIR}/iree"
+echo "Build Directory: ${iree_build_dir}"
+cmake --build "${iree_build_dir}" --target iree-test-deps
+build_tools/cmake/ctest_all.sh "${iree_build_dir}"
diff --git a/build_tools/cmake/iree_bytecode_module.cmake b/build_tools/cmake/iree_bytecode_module.cmake
index 1286628..3be5838 100644
--- a/build_tools/cmake/iree_bytecode_module.cmake
+++ b/build_tools/cmake/iree_bytecode_module.cmake
@@ -83,39 +83,46 @@
   # Users can add their own additional directories as needed.
   list(APPEND _ARGS "--iree-hal-executable-object-search-path=\"${IREE_BINARY_DIR}\"")
 
-  # If an LLVM CPU backend is enabled, supply the linker tool.
-  if(IREE_LLD_TARGET)
-    set(_LINKER_TOOL_EXECUTABLE "$<TARGET_FILE:${IREE_LLD_TARGET}>")
-    list(APPEND _ARGS "--iree-llvmcpu-embedded-linker-path=\"${_LINKER_TOOL_EXECUTABLE}\"")
-    list(APPEND _ARGS "--iree-llvmcpu-wasm-linker-path=\"${_LINKER_TOOL_EXECUTABLE}\"")
-    # Note: --iree-llvmcpu-system-linker-path is left unspecified.
-  endif()
-
-  if(IREE_BYTECODE_MODULE_FORCE_LLVM_SYSTEM_LINKER)
-    list(APPEND _ARGS "--iree-llvmcpu-link-embedded=false")
-  endif()
-
-  if(IREE_BYTECODE_MODULE_ENABLE_ASAN)
-    list(APPEND _ARGS "--iree-llvmcpu-sanitize=address")
-  endif()
-
-  # Support testing in TSan build dirs. Unlike other sanitizers, TSan is an
-  # ABI break: when the host code is built with TSan, the module must be too,
-  # otherwise we get crashes calling module code.
-  if(IREE_BYTECODE_MODULE_ENABLE_TSAN)
-    list(APPEND _ARGS "--iree-llvmcpu-sanitize=thread")
-  endif()
-
   set(_OUTPUT_FILES "${_MODULE_FILE_NAME}")
-  # Check LLVM static library setting. If the static libary output path is set,
-  # retrieve the object path and the corresponding header file path.
-  if(_RULE_STATIC_LIB_PATH)
-    list(APPEND _ARGS "--iree-llvmcpu-link-embedded=false")
-    list(APPEND _ARGS "--iree-llvmcpu-link-static")
-    list(APPEND _ARGS "--iree-llvmcpu-static-library-output-path=${_RULE_STATIC_LIB_PATH}")
 
-    string(REPLACE ".o" ".h" _STATIC_HDR_PATH "${_RULE_STATIC_LIB_PATH}")
-    list(APPEND _OUTPUT_FILES "${_RULE_STATIC_LIB_PATH}" "${_STATIC_HDR_PATH}")
+  # If the llvm-cpu backend is used, pass build-system-dependent flags.
+  #
+  # This crude check for target backend llvm-cpu is borrowed from
+  # iree_compile_flags_for_platform(). This should be made more robust and
+  # shared in a common helper.
+  if (_RULE_FLAGS MATCHES "iree-hal-target-backends=llvm-cpu")
+    if (IREE_LLD_BINARY)
+      # Pass build-system-dependent linker paths.
+      list(APPEND _ARGS "--iree-llvmcpu-embedded-linker-path=\"${IREE_LLD_BINARY}\"")
+      list(APPEND _ARGS "--iree-llvmcpu-wasm-linker-path=\"${IREE_LLD_BINARY}\"")
+    endif()
+    # Note: --iree-llvmcpu-system-linker-path is left unspecified.
+
+    if(IREE_BYTECODE_MODULE_FORCE_LLVM_SYSTEM_LINKER)
+      list(APPEND _ARGS "--iree-llvmcpu-link-embedded=false")
+    endif()
+
+    if(IREE_BYTECODE_MODULE_ENABLE_ASAN)
+      list(APPEND _ARGS "--iree-llvmcpu-sanitize=address")
+    endif()
+
+    # Support testing in TSan build dirs. Unlike other sanitizers, TSan is an
+    # ABI break: when the host code is built with TSan, the module must be too,
+    # otherwise we get crashes calling module code.
+    if(IREE_BYTECODE_MODULE_ENABLE_TSAN)
+      list(APPEND _ARGS "--iree-llvmcpu-sanitize=thread")
+    endif()
+
+    # Check LLVM static library setting. If the static libary output path is set,
+    # retrieve the object path and the corresponding header file path.
+    if(_RULE_STATIC_LIB_PATH)
+      list(APPEND _ARGS "--iree-llvmcpu-link-embedded=false")
+      list(APPEND _ARGS "--iree-llvmcpu-link-static")
+      list(APPEND _ARGS "--iree-llvmcpu-static-library-output-path=${_RULE_STATIC_LIB_PATH}")
+
+      string(REPLACE ".o" ".h" _STATIC_HDR_PATH "${_RULE_STATIC_LIB_PATH}")
+      list(APPEND _OUTPUT_FILES "${_RULE_STATIC_LIB_PATH}" "${_STATIC_HDR_PATH}")
+    endif()
   endif()
 
   iree_compile_flags_for_platform(_PLATFORM_FLAGS "${_RULE_FLAGS}")
diff --git a/build_tools/cmake/iree_llvm.cmake b/build_tools/cmake/iree_llvm.cmake
index 2235228..1731800 100644
--- a/build_tools/cmake/iree_llvm.cmake
+++ b/build_tools/cmake/iree_llvm.cmake
@@ -68,6 +68,7 @@
   set(LLVM_EXTERNAL_LIT "${IREE_SOURCE_DIR}/third_party/llvm-project/llvm/utils/lit/lit.py")
 
   set(IREE_LLVM_LINK_BINARY "$<TARGET_FILE:${IREE_LLVM_LINK_TARGET}>")
+  set(IREE_LLD_BINARY "$<TARGET_FILE:${IREE_LLD_TARGET}>")
   set(IREE_CLANG_BINARY "$<TARGET_FILE:${IREE_CLANG_TARGET}>")
   set(IREE_CLANG_BUILTIN_HEADERS_PATH "${LLVM_BINARY_DIR}/lib/clang/${CLANG_EXECUTABLE_VERSION}/include/")
 endmacro()
@@ -96,6 +97,7 @@
   endif()
 
   set(IREE_LLVM_LINK_BINARY "${LLVM_INSTALL_DIR}/llvm/bin/llvm-link${CMAKE_EXECUTABLE_SUFFIX}")
+  set(IREE_LLD_BINARY "${LLVM_INSTALL_DIR}/llvm/bin/lld${CMAKE_EXECUTABLE_SUFFIX}")
   set(IREE_CLANG_BINARY "${LLVM_INSTALL_DIR}/llvm/bin/clang${CMAKE_EXECUTABLE_SUFFIX}")
   string(REGEX REPLACE "[^0-9].*" "" _LLVM_VERSION_MAJOR "${LLVM_VERSION}")
   set(IREE_CLANG_BUILTIN_HEADERS_PATH "${LLVM_INSTALL_DIR}/llvm/lib/clang/${_LLVM_VERSION_MAJOR}/include/")
@@ -138,6 +140,7 @@
 
   # Unconditionally enable some other cheap LLVM tooling.
   set(IREE_LLVM_LINK_TARGET llvm-link)
+  set(IREE_LLD_TARGET lld)
 
   # Unconditionally enable mlir.
   list(APPEND LLVM_ENABLE_PROJECTS mlir)
diff --git a/build_tools/scripts/byo_llvm.sh b/build_tools/scripts/byo_llvm.sh
index eef42f0..f5ebdff 100755
--- a/build_tools/scripts/byo_llvm.sh
+++ b/build_tools/scripts/byo_llvm.sh
@@ -14,6 +14,9 @@
 #   byo_llvm.sh build_mlir && \
 #   byo_llvm.sh build_iree
 #
+# Additionally, to run tests:
+#   byo_llvm.sh test_iree
+#
 # This script has minimal configurability, which can be extended as needed. The
 # defaults should suffice for testing on CI. Different configurations are
 # possible (i.e. building MLIR bundled with LLVM vs standalone), and this
@@ -27,9 +30,21 @@
 
 TD="$(cd $(dirname $0) && pwd)"
 REPO_ROOT="$(cd $TD/../.. && pwd)"
+
 LLVM_SOURCE_DIR="${LLVM_SOURCE_DIR:-${REPO_ROOT}/third_party/llvm-project}"
-LLVM_BUILD_DIR="${LLVM_BUILD_DIR:-${REPO_ROOT}/../iree-llvm-build}"
-LLVM_INSTALL_DIR="${LLVM_INSTALL_DIR:-${REPO_ROOT}/../iree-llvm-install}"
+IREE_BYOLLVM_BUILD_DIR="${IREE_BYOLLVM_BUILD_DIR:-${REPO_ROOT}/../iree-byollvm-build}"
+IREE_BYOLLVM_INSTALL_DIR="${IREE_BYOLLVM_INSTALL_DIR:-${REPO_ROOT}/../iree-byollvm-install}"
+
+# Canonicalize as absolute paths. These end up in CMake variables such as
+# CMAKE_MODULE_PATH, where relative paths are a footgun as CMake gets invoked
+# from different directories.
+LLVM_SOURCE_DIR="$(realpath -m "${LLVM_SOURCE_DIR}")"
+IREE_BYOLLVM_BUILD_DIR="$(realpath -m "${IREE_BYOLLVM_BUILD_DIR}")"
+IREE_BYOLLVM_INSTALL_DIR="$(realpath -m "${IREE_BYOLLVM_INSTALL_DIR}")"
+echo "Paths canonicalized as:"
+echo "LLVM_SOURCE_DIR=${LLVM_SOURCE_DIR}"
+echo "IREE_BYOLLVM_BUILD_DIR=${IREE_BYOLLVM_BUILD_DIR}"
+echo "IREE_BYOLLVM_INSTALL_DIR=${IREE_BYOLLVM_INSTALL_DIR}"
 
 command="$1"
 shift
@@ -67,8 +82,8 @@
 
 do_build_llvm() {
   echo "*********************** BUILDING LLVM *********************************"
-  main_build_dir="${LLVM_BUILD_DIR}/llvm"
-  main_install_dir="${LLVM_INSTALL_DIR}/llvm"
+  main_build_dir="${IREE_BYOLLVM_BUILD_DIR}/llvm"
+  main_install_dir="${IREE_BYOLLVM_INSTALL_DIR}/llvm"
   targets_to_build="${LLVM_TARGETS_TO_BUILD:-X86}"
   enable_projects="${LLVM_ENABLE_TARGETS:-clang;lld}"
 
@@ -93,9 +108,9 @@
 
 do_build_mlir() {
   echo "*********************** BUILDING MLIR *********************************"
-  main_install_dir="${LLVM_INSTALL_DIR}/llvm"
-  mlir_build_dir="${LLVM_BUILD_DIR}/mlir"
-  mlir_install_dir="${LLVM_INSTALL_DIR}/mlir"
+  main_install_dir="${IREE_BYOLLVM_INSTALL_DIR}/llvm"
+  mlir_build_dir="${IREE_BYOLLVM_BUILD_DIR}/mlir"
+  mlir_install_dir="${IREE_BYOLLVM_INSTALL_DIR}/mlir"
 
   cmake_options="-DLLVM_DIR='${main_install_dir}/lib/cmake/llvm'"
   cmake_options="${cmake_options} -DPython3_EXECUTABLE='$(which $python3_command)'"
@@ -118,14 +133,14 @@
 }
 
 print_iree_config() {
-  llvm_cmake_dir="${LLVM_INSTALL_DIR}/llvm/lib/cmake/llvm"
-  lld_cmake_dir="${LLVM_INSTALL_DIR}/llvm/lib/cmake/lld"
-  clang_cmake_dir="${LLVM_INSTALL_DIR}/llvm/lib/cmake/clang"
+  llvm_cmake_dir="${IREE_BYOLLVM_INSTALL_DIR}/llvm/lib/cmake/llvm"
+  lld_cmake_dir="${IREE_BYOLLVM_INSTALL_DIR}/llvm/lib/cmake/lld"
+  clang_cmake_dir="${IREE_BYOLLVM_INSTALL_DIR}/llvm/lib/cmake/clang"
   # TODO: There seem to be utility exports missing from installed MLIR,
   # so using the build tree for now. This isn't great but needs fixing
   # upstream.
-  #mlir_cmake_dir="${LLVM_INSTALL_DIR}/mlir/lib/cmake/mlir"
-  mlir_cmake_dir="${LLVM_BUILD_DIR}/mlir/lib/cmake/mlir"
+  #mlir_cmake_dir="${IREE_BYOLLVM_INSTALL_DIR}/mlir/lib/cmake/mlir"
+  mlir_cmake_dir="${IREE_BYOLLVM_BUILD_DIR}/mlir/lib/cmake/mlir"
 
   if ! [ -d "$llvm_cmake_dir" ]; then
     echo "WARNING: CMake dir does not exist ($llvm_cmake_dir)" >&2
@@ -144,18 +159,24 @@
     return 1
   fi
 
-  echo "-DLLVM_DIR='$llvm_cmake_dir' -DLLD_DIR='$lld_cmake_dir' -DCLANG_DIR='$clang_cmake_dir' -DMLIR_DIR='$mlir_cmake_dir' -DIREE_BUILD_BUNDLED_LLVM=OFF -DLLVM_INSTALL_DIR=${LLVM_INSTALL_DIR}"
+  echo "-DLLVM_DIR='$llvm_cmake_dir' -DLLD_DIR='$lld_cmake_dir' -DMLIR_DIR='$mlir_cmake_dir' -DIREE_BUILD_BUNDLED_LLVM=OFF -DLLVM_INSTALL_DIR=${IREE_BYOLLVM_INSTALL_DIR}"
 }
 
 do_build_iree() {
   echo "*********************** BUILDING IREE *********************************"
-  iree_build_dir="${LLVM_BUILD_DIR}/iree"
-  iree_install_dir="${LLVM_INSTALL_DIR}/iree"
+  iree_build_dir="${IREE_BYOLLVM_BUILD_DIR}/iree"
+  iree_install_dir="${IREE_BYOLLVM_INSTALL_DIR}/iree"
 
   cmake_options="$(print_iree_config)"
   cmake_options="${cmake_options} -DPython3_EXECUTABLE='$(which $python3_command)'"
   cmake_options="${cmake_options} -DIREE_BUILD_PYTHON_BINDINGS=ON"
+  # Feel free to manually enable or disable any backend, for example
+  #   -DIREE_TARGET_BACKEND_LLVM_CPU=OFF
+  # Be aware though that several tests in IREE's own suite are currently
+  # assuming that certain backends are enabled (#14034), so that may cause test
+  # failures, but that's a test-only issue.
   cmake_options="${cmake_options} -DIREE_TARGET_BACKEND_DEFAULTS=OFF"
+  cmake_options="${cmake_options} -DIREE_TARGET_BACKEND_LLVM_CPU=ON"
   cmake_options="${cmake_options} -DIREE_HAL_DRIVER_DEFAULTS=OFF"
   cmake_options="${cmake_options} -DIREE_HAL_DRIVER_LOCAL_SYNC=ON"
   cmake_options="${cmake_options} -DIREE_HAL_DRIVER_LOCAL_TASK=ON"
@@ -175,6 +196,18 @@
   cmake -DCMAKE_INSTALL_PREFIX="${iree_install_dir}" -P "${iree_build_dir}/cmake_install.cmake"
 }
 
+do_test_iree() {
+  echo "*********************** TESTING IREE **********************************"
+  iree_build_dir="${IREE_BYOLLVM_BUILD_DIR}/iree"
+  iree_install_dir="${IREE_BYOLLVM_INSTALL_DIR}/iree"
+
+  echo "Source Directory: ${REPO_ROOT}"
+  echo "Build Directory: ${iree_build_dir}"
+
+  cmake --build "${iree_build_dir}" --target iree-test-deps
+  "${REPO_ROOT}/build_tools/cmake/ctest_all.sh" "${iree_build_dir}"
+}
+
 case "${command}" in
   build_llvm)
     do_build_llvm
@@ -188,6 +221,10 @@
     do_build_iree
     ;;
 
+  test_iree)
+    do_test_iree
+    ;;
+
   *)
     echo "ERROR: Expected command of 'build_llvm', 'clean_install' (got '${command}')"
     exit 1
diff --git a/compiler/bindings/python/test/tools/CMakeLists.txt b/compiler/bindings/python/test/tools/CMakeLists.txt
index d5cbeb0..427116b 100644
--- a/compiler/bindings/python/test/tools/CMakeLists.txt
+++ b/compiler/bindings/python/test/tools/CMakeLists.txt
@@ -4,12 +4,16 @@
 # See https://llvm.org/LICENSE.txt for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+# These tests perform linking via the Compiler API, which is only supported
+# in bundled-LLVM builds at the moment (#14086).
+if(IREE_BUILD_BUNDLED_LLVM)
 iree_py_test(
   NAME
     compiler_core_test
   SRCS
     "compiler_core_test.py"
 )
+endif()  # IREE_BUILD_BUNDLED_LLVM
 
 iree_py_test(
   NAME
diff --git a/compiler/src/iree/compiler/API/Internal/CMakeLists.txt b/compiler/src/iree/compiler/API/Internal/CMakeLists.txt
index a23eb72..aabf542 100644
--- a/compiler/src/iree/compiler/API/Internal/CMakeLists.txt
+++ b/compiler/src/iree/compiler/API/Internal/CMakeLists.txt
@@ -92,7 +92,8 @@
 set(_lld_copts)
 set(_lld_deps)
 
-if(IREE_LLD_TARGET)
+# TODO(#14086): implement linking in the installed-LLVM case.
+if(IREE_BUILD_BUNDLED_LLVM)
   # Use generator expressions to conditionally include LLD targets, providing
   # a compiler definition if not available.
   macro(_add_dependent_lld_target target not_definition)
diff --git a/experimental/cpu_ukernel/iree_experimental_standalone_plugin.cmake b/experimental/cpu_ukernel/iree_experimental_standalone_plugin.cmake
index 516a2a1..3269900 100644
--- a/experimental/cpu_ukernel/iree_experimental_standalone_plugin.cmake
+++ b/experimental/cpu_ukernel/iree_experimental_standalone_plugin.cmake
@@ -78,11 +78,11 @@
       DEPENDS
         "${_SRC_FILE}"
         "${IREE_CLANG_TARGET}"
-      COMMAND "${IREE_CLANG_TARGET}"
+      COMMAND "${IREE_CLANG_BINARY}"
         # Flags copied from
         # compiler/src/iree/compiler/Dialect/HAL/Target/LLVMCPU/internal/EmbeddedLinkerTool.cpp
         -target "${LLVM_ARCH}-unknown-unknown-eabi-elf"
-        -isystem "${IREE_BINARY_DIR}/third_party/llvm-project/llvm/lib/clang/17/include"
+        -isystem "${IREE_CLANG_BUILTIN_HEADERS_PATH}"
         -std=c17
         -fasm  # Added for inline-asm support.
         -fPIC
@@ -110,7 +110,7 @@
     DEPENDS
       ${_OBJECT_FILES}
       ${IREE_LLD_TARGET}
-    COMMAND ${IREE_LLD_TARGET}
+    COMMAND ${IREE_LLD_BINARY}
       -flavor gnu
       --build-id=none
       -nostdlib
diff --git a/runtime/bindings/python/CMakeLists.txt b/runtime/bindings/python/CMakeLists.txt
index 1f8b5aa..c78fe5b 100644
--- a/runtime/bindings/python/CMakeLists.txt
+++ b/runtime/bindings/python/CMakeLists.txt
@@ -153,24 +153,28 @@
 
 iree_py_test(
   NAME
-    system_api_test
-  SRCS
-    "tests/system_api_test.py"
-)
-
-iree_py_test(
-  NAME
     system_setup_test
   SRCS
     "tests/system_setup_test.py"
 )
 
-iree_py_test(
-  NAME
-    vm_test
-  SRCS
-    "tests/vm_test.py"
-)
+# These tests perform linking via the Compiler API, which is only supported
+# in bundled-LLVM builds at the moment (#14086).
+if(IREE_BUILD_BUNDLED_LLVM)
+  iree_py_test(
+    NAME
+      system_api_test
+    SRCS
+      "tests/system_api_test.py"
+  )
+
+  iree_py_test(
+    NAME
+      vm_test
+    SRCS
+      "tests/vm_test.py"
+  )
+endif()
 
 iree_py_test(
   NAME
diff --git a/samples/custom_dispatch/cpu/embedded/CMakeLists.txt b/samples/custom_dispatch/cpu/embedded/CMakeLists.txt
index ea6a02d..62993c0 100644
--- a/samples/custom_dispatch/cpu/embedded/CMakeLists.txt
+++ b/samples/custom_dispatch/cpu/embedded/CMakeLists.txt
@@ -34,9 +34,9 @@
     DEPENDS
       functions.c
       ${IREE_CLANG_TARGET}
-    COMMAND ${IREE_CLANG_TARGET}
+    COMMAND ${IREE_CLANG_BINARY}
       -target ${_LLVM_ARCH}-unknown-unknown-eabi-elf
-      -isystem ${IREE_BINARY_DIR}/third_party/llvm-project/llvm/lib/clang/17/include
+      -isystem ${IREE_CLANG_BUILTIN_HEADERS_PATH}
       -std=c17
       -ffreestanding
       -fvisibility=hidden
diff --git a/samples/custom_dispatch/cpu/plugin/CMakeLists.txt b/samples/custom_dispatch/cpu/plugin/CMakeLists.txt
index b1a3858..73df2dd 100644
--- a/samples/custom_dispatch/cpu/plugin/CMakeLists.txt
+++ b/samples/custom_dispatch/cpu/plugin/CMakeLists.txt
@@ -100,9 +100,9 @@
     DEPENDS
       standalone_plugin.c
       ${IREE_CLANG_TARGET}
-    COMMAND ${IREE_CLANG_TARGET}
+    COMMAND ${IREE_CLANG_BINARY}
       -target ${LLVM_ARCH}-unknown-unknown-eabi-elf
-      -isystem ${IREE_BINARY_DIR}/third_party/llvm-project/llvm/lib/clang/17/include
+      -isystem ${IREE_CLANG_BUILTIN_HEADERS_PATH}
       -std=c17
       -fPIC
       -ffreestanding
@@ -125,7 +125,7 @@
     DEPENDS
       standalone_plugin_${_ARCH}.o
       ${IREE_LLD_TARGET}
-    COMMAND ${IREE_LLD_TARGET}
+    COMMAND ${IREE_LLD_BINARY}
       -flavor gnu
       --build-id=none
       -nostdlib
diff --git a/samples/custom_dispatch/cuda/kernels/CMakeLists.txt b/samples/custom_dispatch/cuda/kernels/CMakeLists.txt
index a5d675c..b64999a 100644
--- a/samples/custom_dispatch/cuda/kernels/CMakeLists.txt
+++ b/samples/custom_dispatch/cuda/kernels/CMakeLists.txt
@@ -87,7 +87,7 @@
     DEPENDS
       ${_PTX_SRC_NAME}
       ${IREE_CLANG_TARGET}
-    COMMAND ${IREE_CLANG_TARGET}
+    COMMAND ${IREE_CLANG_BINARY}
       -x cuda
       -Wno-unknown-cuda-version
       --cuda-path=${CUDAToolkit_ROOT}
diff --git a/samples/py_custom_module/CMakeLists.txt b/samples/py_custom_module/CMakeLists.txt
index 8a55265..5e1053d 100644
--- a/samples/py_custom_module/CMakeLists.txt
+++ b/samples/py_custom_module/CMakeLists.txt
@@ -8,6 +8,12 @@
   return()
 endif()
 
+# These tests perform linking via the Compiler API, which is only supported
+# in bundled-LLVM builds at the moment (#14086).
+if(NOT IREE_BUILD_BUNDLED_LLVM)
+  return()
+endif()
+
 iree_py_test(
   NAME
     decode_secret_message_sample
diff --git a/tests/e2e/models/mnist_train_test/CMakeLists.txt b/tests/e2e/models/mnist_train_test/CMakeLists.txt
index 2a16aa0..a2ff8ef 100644
--- a/tests/e2e/models/mnist_train_test/CMakeLists.txt
+++ b/tests/e2e/models/mnist_train_test/CMakeLists.txt
@@ -4,6 +4,12 @@
 # See https://llvm.org/LICENSE.txt for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
+# These tests perform linking via the Compiler API, which is only supported
+# in bundled-LLVM builds at the moment (#14086).
+if(NOT IREE_BUILD_BUNDLED_LLVM)
+  return()
+endif()
+
 if(IREE_TARGET_BACKEND_LLVM_CPU AND IREE_HAL_DRIVER_LOCAL_TASK)
   iree_py_test(
     NAME