Replace CMake `iree_get_executable_path()` with `IMPORTED` targets. (#11764)

This changes how to access host tools during CMake's configure step from
using a custom `iree_get_executable_path()` function to using `IMPORTED`
targets
(https://cmake.org/cmake/help/latest/guide/importing-exporting/index.html).
A new helper `iree_import_binary` is added to keep
`IREE_HOST_BINARY_ROOT` use centralized.

## Details

Previously, each CMake file that wanted to use a tool that might be
provided by either the current build or a "host install" would use the
`iree_get_executable_path` function, e.g.
```cmake
# tools/CMakeLists.txt
if(IREE_BUILD_COMPILER)
  iree_cc_binary(NAME iree-compile ...)
endif()

# some other file
iree_get_executable_path(_COMPILE_TOOL_EXECUTABLE "iree-compile")
add_custom_command(
  OUTPUT ${_OUTPUT_FILES}
  COMMAND ${_COMPILE_TOOL_EXECUTABLE} ${_ARGS}
  DEPENDS ${_COMPILE_TOOL_EXECUTABLE} ${_SRC_PATH}
)
```
The `iree_get_executable_path` function attempted to find an executable
with that name and would add an `IMPORTED` target if one did not already
exist.

We can simplify that to just defining `IMPORTED` targets directly, right
where the targets are normally defined when `IREE_BUILD_COMPILER` is
enabled (or `CMAKE_CROSSCOMPILING` is disabled). Importing targets where
they are defined (rather than where they are used) will also let us more
cleanly solve issues like https://github.com/iree-org/iree/issues/11331.

Now this looks more like
```cmake
# tools/CMakeLists.txt
if(IREE_HOST_BINARY_ROOT AND NOT IREE_BUILD_COMPILER)
  iree_import_binary(NAME iree-compile)
elseif(IREE_BUILD_COMPILER)
  iree_cc_binary(NAME iree-compile ...)
else()
  # warn about not building or importing
endif()

# some other file - use the target without caring if it was imported or not
add_custom_command(
  OUTPUT ${_OUTPUT_FILES}
  COMMAND iree-compile ${_ARGS}
  DEPENDS iree-compile ${_SRC_PATH}
)
```

## Other context

This is an alternate take on https://github.com/iree-org/iree/pull/11441
and was discussed a bit here:
https://github.com/iree-org/iree/pull/11402#discussion_r1041471357.

Future PRs may
* Remove the implicit `bin/` suffix from `IREE_HOST_BINARY_ROOT` to
decouple it from the CMake `install` process
* Add `SHARED_LIBRARY_DEPS` to `iree_import_binary`, along with
`iree_import_library`, for proper dependency modeling of
IREECompiler.dll (assuming that `IMPORTED_RUNTIME_ARTIFACTS` doesn't
already do what I want)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c8f8e88..18d6eb6 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -370,6 +370,7 @@
 include(iree_cc_binary)
 include(iree_cc_library)
 include(iree_cc_test)
+include(iree_import_binary)
 include(iree_external_cmake_options)
 include(iree_tablegen_library)
 include(iree_tablegen_doc)
@@ -612,21 +613,13 @@
   endif()
 endif()
 
-add_subdirectory(build_tools/third_party/flatcc EXCLUDE_FROM_ALL)
+# This defines the iree-flatcc-cli target, so we don't use EXCLUDE_FROM_ALL.
+add_subdirectory(build_tools/third_party/flatcc)
 
 if(IREE_HAL_DRIVER_VULKAN)
   add_subdirectory(third_party/vulkan_headers EXCLUDE_FROM_ALL)
 endif()
 
-# TODO(scotttodd): Iterate some more and find a better place for this.
-if(NOT CMAKE_CROSSCOMPILING)
-  install(
-    TARGETS iree-flatcc-cli
-    COMPONENT iree-flatcc-cli
-    RUNTIME DESTINATION bin
-  )
-endif()
-
 if(IREE_BUILD_COMPILER)
   add_subdirectory(build_tools/third_party/mlir-hlo EXCLUDE_FROM_ALL)
 endif()
diff --git a/build_tools/cmake/flatbuffer_c_library.cmake b/build_tools/cmake/flatbuffer_c_library.cmake
index b88c3ae..6e54d6f 100644
--- a/build_tools/cmake/flatbuffer_c_library.cmake
+++ b/build_tools/cmake/flatbuffer_c_library.cmake
@@ -87,12 +87,11 @@
   endforeach()
   list(TRANSFORM _OUTS PREPEND "${CMAKE_CURRENT_BINARY_DIR}/")
 
-  iree_get_executable_path(_FLATCC_BIN iree-flatcc-cli)
   add_custom_command(
     OUTPUT
       ${_OUTS}
     COMMAND
-      "${_FLATCC_BIN}"
+      iree-flatcc-cli
           -o "${CMAKE_CURRENT_BINARY_DIR}"
           -I "${IREE_ROOT_DIR}"
           ${_RULE_FLATCC_ARGS}
diff --git a/build_tools/cmake/iree_benchmark_suite.cmake b/build_tools/cmake/iree_benchmark_suite.cmake
index 4f5902b..035bbba 100644
--- a/build_tools/cmake/iree_benchmark_suite.cmake
+++ b/build_tools/cmake/iree_benchmark_suite.cmake
@@ -190,11 +190,11 @@
   )
 
   # Try to check if the compiler supports the TARGET_BACKEND. If
-  # IREE_HOST_BINARY_ROOT is defined, we are using a compiler binary, in which
-  # case we can't check it's supported backend just by looking at this build
+  # IREE_HOST_BINARY_ROOT is set, we are using a compiler binary, in which
+  # case we can't check it's supported backends just by looking at this build
   # dir's cmake variables --- we would have to implement a configure-check
-  # executing that compiler.
-  if (NOT DEFINED IREE_HOST_BINARY_ROOT)
+  # executing `iree-compile --iree-hal-list-target-backends`.
+  if(NOT DEFINED IREE_HOST_BINARY_ROOT)
     string(TOUPPER ${_RULE_TARGET_BACKEND} _UPPERCASE_TARGET_BACKEND)
     string(REPLACE "-" "_" _NORMALIZED_TARGET_BACKEND ${_UPPERCASE_TARGET_BACKEND})
     if(NOT IREE_TARGET_BACKEND_${_NORMALIZED_TARGET_BACKEND})
diff --git a/build_tools/cmake/iree_bytecode_module.cmake b/build_tools/cmake/iree_bytecode_module.cmake
index a83bca0..6120951 100644
--- a/build_tools/cmake/iree_bytecode_module.cmake
+++ b/build_tools/cmake/iree_bytecode_module.cmake
@@ -68,8 +68,6 @@
     set(_MODULE_FILE_NAME "${_RULE_NAME}.vmfb")
   endif()
 
-  iree_get_executable_path(_COMPILE_TOOL_EXECUTABLE ${_COMPILE_TOOL})
-
   set(_ARGS "--output-format=vm-bytecode")
   list(APPEND _ARGS "${_RULE_FLAGS}")
 
@@ -84,7 +82,7 @@
 
   # If an LLVM CPU backend is enabled, supply the linker tool.
   if(IREE_LLD_TARGET)
-    iree_get_executable_path(_LINKER_TOOL_EXECUTABLE "lld")
+    set(_LINKER_TOOL_EXECUTABLE "$<TARGET_FILE:${IREE_LLD_TARGET}>")
     list(APPEND _ARGS "--iree-llvm-embedded-linker-path=\"${_LINKER_TOOL_EXECUTABLE}\"")
     list(APPEND _ARGS "--iree-llvm-wasm-linker-path=\"${_LINKER_TOOL_EXECUTABLE}\"")
     # Note: --iree-llvm-system-linker-path is left unspecified.
@@ -130,11 +128,10 @@
     OUTPUT
       ${_OUTPUT_FILES}
     COMMAND
-      ${_COMPILE_TOOL_EXECUTABLE}
+      ${_COMPILE_TOOL}
       ${_ARGS}
-    # Changes to either the compiler tool or the input sources should rebuild.
     DEPENDS
-      ${_COMPILE_TOOL_EXECUTABLE}
+      ${_COMPILE_TOOL}
       ${_LINKER_TOOL_EXECUTABLE}
       ${_RULE_SRC}
       ${_RULE_DEPENDS}
diff --git a/build_tools/cmake/iree_c_embed_data.cmake b/build_tools/cmake/iree_c_embed_data.cmake
index 1d202fd..adcf8d0 100644
--- a/build_tools/cmake/iree_c_embed_data.cmake
+++ b/build_tools/cmake/iree_c_embed_data.cmake
@@ -70,12 +70,10 @@
     list(APPEND _ARGS "${_SRC}")
   endforeach(_SRC)
 
-  iree_get_executable_path(_EXE_PATH generate_embed_data)
-
   add_custom_command(
     OUTPUT "${_RULE_H_FILE_OUTPUT}" "${_RULE_C_FILE_OUTPUT}"
-    COMMAND ${_EXE_PATH} ${_ARGS}
-    DEPENDS ${_EXE_PATH} ${_RULE_SRCS} ${_RULE_GENERATED_SRCS}
+    COMMAND generate_embed_data ${_ARGS}
+    DEPENDS generate_embed_data ${_RULE_SRCS} ${_RULE_GENERATED_SRCS}
   )
 
   if(_RULE_TESTONLY)
diff --git a/build_tools/cmake/iree_c_module.cmake b/build_tools/cmake/iree_c_module.cmake
index ca74060..65b8c22 100644
--- a/build_tools/cmake/iree_c_module.cmake
+++ b/build_tools/cmake/iree_c_module.cmake
@@ -60,7 +60,6 @@
     set(_COMPILE_TOOL "iree-compile")
   endif()
 
-  iree_get_executable_path(_COMPILE_TOOL_EXECUTABLE ${_COMPILE_TOOL})
   get_filename_component(_SRC_PATH "${_RULE_SRC}" REALPATH)
 
   set(_ARGS "--output-format=vm-c")
@@ -88,9 +87,8 @@
 
   add_custom_command(
     OUTPUT ${_OUTPUT_FILES}
-    COMMAND ${_COMPILE_TOOL_EXECUTABLE} ${_ARGS}
-    # Changes to either the compiler tool or the input source should rebuild.
-    DEPENDS ${_COMPILE_TOOL_EXECUTABLE} ${_SRC_PATH}
+    COMMAND ${_COMPILE_TOOL} ${_ARGS}
+    DEPENDS ${_COMPILE_TOOL} ${_SRC_PATH}
   )
 
   iree_cc_library(
diff --git a/build_tools/cmake/iree_copts.cmake b/build_tools/cmake/iree_copts.cmake
index f0612cb..f347baf 100644
--- a/build_tools/cmake/iree_copts.cmake
+++ b/build_tools/cmake/iree_copts.cmake
@@ -432,8 +432,9 @@
 #-------------------------------------------------------------------------------
 # Third party: llvm-project
 #-------------------------------------------------------------------------------
+
 if(IREE_BUILD_COMPILER)
-  # iree-tblgen is not defined using the add_tablegen mechanism as other TableGen
-  # tools in LLVM.
-  iree_get_executable_path(IREE_TABLEGEN_EXE iree-tblgen)
+  # iree-tblgen is not defined using the add_tablegen mechanism as other
+  # TableGen tools in LLVM.
+  set(IREE_TABLEGEN_EXE "$<TARGET_FILE:iree-tblgen>")
 endif()
diff --git a/build_tools/cmake/iree_import_binary.cmake b/build_tools/cmake/iree_import_binary.cmake
new file mode 100644
index 0000000..30f286f
--- /dev/null
+++ b/build_tools/cmake/iree_import_binary.cmake
@@ -0,0 +1,61 @@
+# 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
+
+include(CMakeParseArguments)
+
+# iree_import_binary()
+#
+# CMake function to import an executable/binary file into a CMake target.
+# This imports from the directory specified by IREE_HOST_BINARY_ROOT/bin, and
+# that variable _must_ be set for calls to the function to be valid.
+#
+# Parameters:
+# NAME: name of target/binary (see Usage below)
+#
+# Usage:
+#   if(BUILD_AWESOME_TOOL)
+#     iree_cc_binary(
+#       NAME awesome-tool
+#       SRCS "awesome-tool-main.cc"
+#     )
+#   elseif(DEFINED IREE_HOST_BINARY_ROOT)
+#     # Import '${IREE_HOST_BINARY_ROOT}/bin/awesome-tool[.exe]' into the
+#     # CMake target 'awesome-tool'.
+#     iree_import_binary(NAME awesome-tool)
+#   else()
+#     message(STATUS "Not building or importing awesome-tool")
+#   endif()
+function(iree_import_binary)
+  cmake_parse_arguments(
+    _RULE
+    ""
+    "NAME"
+    ""
+    ${ARGN}
+  )
+
+  # TODO(scotttodd): optional 'TARGET' argument (that defaults to NAME)
+  # TODO(scotttodd): remove /bin suffix from this rule and update all scripts/docs
+  # TODO(scotttodd): SHARED_LIBRARY_DEPS argument?
+
+  if(NOT DEFINED IREE_HOST_BINARY_ROOT)
+    message(FATAL_ERROR "IREE_HOST_BINARY_ROOT must be set to use iree_import_binary")
+  endif()
+
+  set(_FULL_BINARY_NAME "${_RULE_NAME}${CMAKE_EXECUTABLE_SUFFIX}")
+  set(_BINARY_PATH "${IREE_HOST_BINARY_ROOT}/bin/${_FULL_BINARY_NAME}")
+  file(REAL_PATH "${_BINARY_PATH}" _BINARY_PATH
+       BASE_DIRECTORY ${IREE_ROOT_DIR} EXPAND_TILDE)
+
+  if(NOT EXISTS ${_BINARY_PATH})
+    message(FATAL_ERROR "Could not find '${_FULL_BINARY_NAME}' under "
+            "'${IREE_HOST_BINARY_ROOT}/bin'\n(Expanded to '${_BINARY_PATH}').\n"
+            "Ensure that IREE_HOST_BINARY_ROOT points to a complete install directory.")
+  endif()
+
+  add_executable(${_RULE_NAME} IMPORTED GLOBAL)
+  set_property(TARGET "${_RULE_NAME}" PROPERTY IMPORTED_LOCATION "${_BINARY_PATH}")
+endfunction()
diff --git a/build_tools/cmake/iree_macros.cmake b/build_tools/cmake/iree_macros.cmake
index 3e90931..68b6fa3 100644
--- a/build_tools/cmake/iree_macros.cmake
+++ b/build_tools/cmake/iree_macros.cmake
@@ -129,39 +129,6 @@
   set(${PACKAGE_DIR} ${_PACKAGE_DIR} PARENT_SCOPE)
 endfunction()
 
-# iree_get_executable_path
-#
-# Gets the path to an executable in a cross-compilation-aware way. This
-# should be used when accessing binaries that are used as part of the build,
-# such as for generating files used for later build steps.
-#
-# Paramters:
-# - OUTPUT_PATH_VAR: variable name for receiving the path to the built target.
-# - EXECUTABLE: the executable to get its path. Note that this needs to be the
-#     name of the executable target when not cross compiling and the basename of
-#     the binary when importing a binary from a host build. Thus this should be
-#     the global unqualified name of the binary, not the fully-specified name.
-function(iree_get_executable_path OUTPUT_PATH_VAR EXECUTABLE)
-  if(NOT DEFINED IREE_HOST_BINARY_ROOT OR TARGET "${EXECUTABLE}")
-    # We can either expect the target to be defined as part of this CMake
-    # invocation (if not cross compiling) or the target is defined already.
-    set(${OUTPUT_PATH_VAR} "$<TARGET_FILE:${EXECUTABLE}>" PARENT_SCOPE)
-  else()
-    # The target won't be directly defined by this CMake invocation so check
-    # for an already built executable at IREE_HOST_BINARY_ROOT. If we find it,
-    # add it as an imported target so it gets picked up on later invocations.
-    set(_EXECUTABLE_PATH "${IREE_HOST_BINARY_ROOT}/bin/${EXECUTABLE}${IREE_HOST_EXECUTABLE_SUFFIX}")
-    if(EXISTS ${_EXECUTABLE_PATH})
-      add_executable("${EXECUTABLE}" IMPORTED GLOBAL)
-      set_property(TARGET "${EXECUTABLE}" PROPERTY IMPORTED_LOCATION "${_EXECUTABLE_PATH}")
-      set(${OUTPUT_PATH_VAR} "$<TARGET_FILE:${EXECUTABLE}>" PARENT_SCOPE)
-    else()
-      message(FATAL_ERROR "Could not find '${EXECUTABLE}' at '${_EXECUTABLE_PATH}'. "
-              "Ensure that IREE_HOST_BINARY_ROOT points to installed binaries.")
-    endif()
-  endif()
-endfunction()
-
 #-------------------------------------------------------------------------------
 # select()-like Evaluation
 #-------------------------------------------------------------------------------
diff --git a/build_tools/cmake/iree_microbenchmark_suite.cmake b/build_tools/cmake/iree_microbenchmark_suite.cmake
index 3b2c893..565015f 100644
--- a/build_tools/cmake/iree_microbenchmark_suite.cmake
+++ b/build_tools/cmake/iree_microbenchmark_suite.cmake
@@ -32,7 +32,6 @@
     set(_TRANSLATE_SRC "${_SRC}")
     set(_MODULE_FILE_NAME "${_RULE_NAME}_${_SRC}.vmfb")
     set(_TARGET_NAME "${PACKAGE_NAME}_${_MODULE_FILE_NAME}")
-    iree_get_executable_path(_COMPILE_TOOL_EXECUTABLE "${_COMPILE_TOOL}")
     set(_ARGS "${_RULE_FLAGS}")
     get_filename_component(_TRANSLATE_SRC_PATH "${_TRANSLATE_SRC}" REALPATH)
     list(APPEND _ARGS "${_TRANSLATE_SRC_PATH}")
@@ -43,12 +42,11 @@
       OUTPUT
         "${_MODULE_FILE_NAME}"
       COMMAND
-        "${_COMPILE_TOOL_EXECUTABLE}"
+        ${_COMPILE_TOOL}
         ${_ARGS}
-      # Changes to either the compiler tool or the input source should rebuild.
       DEPENDS
-        "${_COMPILE_TOOL_EXECUTABLE}"
-        "${_TRANSLATE_SRC}"
+        ${_COMPILE_TOOL}
+        ${_TRANSLATE_SRC}
       VERBATIM
     )
     add_custom_target("${_TARGET_NAME}"
diff --git a/build_tools/cmake/iree_static_linker_test.cmake b/build_tools/cmake/iree_static_linker_test.cmake
index 517b8f4..51e4ddf 100644
--- a/build_tools/cmake/iree_static_linker_test.cmake
+++ b/build_tools/cmake/iree_static_linker_test.cmake
@@ -70,8 +70,6 @@
     return()
   endif()
 
-  iree_get_executable_path(_COMPILER_TOOL "iree-compile")
-
   iree_package_name(_PACKAGE_NAME)
   iree_package_ns(_PACKAGE_NS)
   set(_NAME "${_PACKAGE_NAME}_${_RULE_NAME}")
diff --git a/build_tools/embed_data/CMakeLists.txt b/build_tools/embed_data/CMakeLists.txt
index 3654581..3ead83e 100644
--- a/build_tools/embed_data/CMakeLists.txt
+++ b/build_tools/embed_data/CMakeLists.txt
@@ -4,52 +4,60 @@
 # See https://llvm.org/LICENSE.txt for license information.
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
-if(NOT CMAKE_CROSSCOMPILING)
-  add_executable(generate_embed_data)
-  target_sources(generate_embed_data PRIVATE generate_embed_data_main.cc)
-  set_target_properties(generate_embed_data PROPERTIES OUTPUT_NAME generate_embed_data)
-
-  install(TARGETS generate_embed_data
+# Just import if IREE_HOST_BINARY_ROOT is set (e.g. when cross-compiling).
+if(DEFINED IREE_HOST_BINARY_ROOT)
+  iree_import_binary(NAME generate_embed_data)
+  install(IMPORTED_RUNTIME_ARTIFACTS generate_embed_data
           COMPONENT generate_embed_data
           RUNTIME DESTINATION bin
           BUNDLE DESTINATION bin)
-
-  iree_c_embed_data(
-    NAME
-      "testembed1"
-    SRCS
-      "file1.bin"
-      "data/file2.bin"
-    C_FILE_OUTPUT
-      "testembed1.c"
-    H_FILE_OUTPUT
-      "testembed1.h"
-    FLATTEN
-    TESTONLY
-  )
-
-  iree_c_embed_data(
-    NAME
-      "testembed2"
-    SRCS
-      "data/file3.bin"
-    C_FILE_OUTPUT
-      "testembed2.c"
-    H_FILE_OUTPUT
-      "testembed2.h"
-    FLATTEN
-    TESTONLY
-  )
-
-  iree_cc_test(
-    NAME
-      "c_embed_data_test"
-    SRCS
-      "c_embed_data_test.cc"
-    DEPS
-      ::testembed1
-      ::testembed2
-      iree::testing::gtest
-      iree::testing::gtest_main
-  )
+  return()
 endif()
+
+add_executable(generate_embed_data)
+target_sources(generate_embed_data PRIVATE generate_embed_data_main.cc)
+set_target_properties(generate_embed_data PROPERTIES OUTPUT_NAME generate_embed_data)
+
+install(TARGETS generate_embed_data
+        COMPONENT generate_embed_data
+        RUNTIME DESTINATION bin
+        BUNDLE DESTINATION bin)
+
+iree_c_embed_data(
+  NAME
+    "testembed1"
+  SRCS
+    "file1.bin"
+    "data/file2.bin"
+  C_FILE_OUTPUT
+    "testembed1.c"
+  H_FILE_OUTPUT
+    "testembed1.h"
+  FLATTEN
+  TESTONLY
+)
+
+iree_c_embed_data(
+  NAME
+    "testembed2"
+  SRCS
+    "data/file3.bin"
+  C_FILE_OUTPUT
+    "testembed2.c"
+  H_FILE_OUTPUT
+    "testembed2.h"
+  FLATTEN
+  TESTONLY
+)
+
+iree_cc_test(
+  NAME
+    "c_embed_data_test"
+  SRCS
+    "c_embed_data_test.cc"
+  DEPS
+    ::testembed1
+    ::testembed2
+    iree::testing::gtest
+    iree::testing::gtest_main
+)
diff --git a/build_tools/third_party/flatcc/CMakeLists.txt b/build_tools/third_party/flatcc/CMakeLists.txt
index 74caf63..801d411 100644
--- a/build_tools/third_party/flatcc/CMakeLists.txt
+++ b/build_tools/third_party/flatcc/CMakeLists.txt
@@ -79,42 +79,52 @@
   PUBLIC
 )
 
-# Define our own binary target for the CLI, for host builds only.
+if(DEFINED IREE_HOST_BINARY_ROOT)
+  # Just import if IREE_HOST_BINARY_ROOT is set (e.g. when cross-compiling).
+  iree_import_binary(NAME iree-flatcc-cli)
+  return()
+endif()
+
+# Define our own binary target for the CLI.
 # flatcc's `flatcc_cli` target renames itself to `flatcc` or `flatcc_d`
 # depending on the build configuration, so we prefer to just set our own name.
-if(NOT CMAKE_CROSSCOMPILING)
-  add_executable(iree-flatcc-cli
-    "${FLATCC_ROOT}/src/cli/flatcc_cli.c"
-    "${FLATCC_ROOT}/external/hash/cmetrohash64.c"
-    "${FLATCC_ROOT}/external/hash/str_set.c"
-    "${FLATCC_ROOT}/external/hash/ptr_set.c"
-    "${FLATCC_ROOT}/src/compiler/hash_tables/symbol_table.c"
-    "${FLATCC_ROOT}/src/compiler/hash_tables/scope_table.c"
-    "${FLATCC_ROOT}/src/compiler/hash_tables/name_table.c"
-    "${FLATCC_ROOT}/src/compiler/hash_tables/schema_table.c"
-    "${FLATCC_ROOT}/src/compiler/hash_tables/value_set.c"
-    "${FLATCC_ROOT}/src/compiler/fileio.c"
-    "${FLATCC_ROOT}/src/compiler/parser.c"
-    "${FLATCC_ROOT}/src/compiler/semantics.c"
-    "${FLATCC_ROOT}/src/compiler/coerce.c"
-    "${FLATCC_ROOT}/src/compiler/codegen_schema.c"
-    "${FLATCC_ROOT}/src/compiler/flatcc.c"
-    "${FLATCC_ROOT}/src/compiler/codegen_c.c"
-    "${FLATCC_ROOT}/src/compiler/codegen_c_reader.c"
-    "${FLATCC_ROOT}/src/compiler/codegen_c_sort.c"
-    "${FLATCC_ROOT}/src/compiler/codegen_c_builder.c"
-    "${FLATCC_ROOT}/src/compiler/codegen_c_verifier.c"
-    "${FLATCC_ROOT}/src/compiler/codegen_c_sorter.c"
-    "${FLATCC_ROOT}/src/compiler/codegen_c_json_parser.c"
-    "${FLATCC_ROOT}/src/compiler/codegen_c_json_printer.c"
-    "${FLATCC_ROOT}/src/runtime/builder.c"
-    "${FLATCC_ROOT}/src/runtime/emitter.c"
-    "${FLATCC_ROOT}/src/runtime/refmap.c"
-  )
-  target_include_directories(iree-flatcc-cli SYSTEM
-    PUBLIC
-      "${FLATCC_ROOT}/external"
-      "${FLATCC_ROOT}/include"
-      "${FLATCC_ROOT}/config"
-  )
-endif()
+add_executable(iree-flatcc-cli
+  "${FLATCC_ROOT}/src/cli/flatcc_cli.c"
+  "${FLATCC_ROOT}/external/hash/cmetrohash64.c"
+  "${FLATCC_ROOT}/external/hash/str_set.c"
+  "${FLATCC_ROOT}/external/hash/ptr_set.c"
+  "${FLATCC_ROOT}/src/compiler/hash_tables/symbol_table.c"
+  "${FLATCC_ROOT}/src/compiler/hash_tables/scope_table.c"
+  "${FLATCC_ROOT}/src/compiler/hash_tables/name_table.c"
+  "${FLATCC_ROOT}/src/compiler/hash_tables/schema_table.c"
+  "${FLATCC_ROOT}/src/compiler/hash_tables/value_set.c"
+  "${FLATCC_ROOT}/src/compiler/fileio.c"
+  "${FLATCC_ROOT}/src/compiler/parser.c"
+  "${FLATCC_ROOT}/src/compiler/semantics.c"
+  "${FLATCC_ROOT}/src/compiler/coerce.c"
+  "${FLATCC_ROOT}/src/compiler/codegen_schema.c"
+  "${FLATCC_ROOT}/src/compiler/flatcc.c"
+  "${FLATCC_ROOT}/src/compiler/codegen_c.c"
+  "${FLATCC_ROOT}/src/compiler/codegen_c_reader.c"
+  "${FLATCC_ROOT}/src/compiler/codegen_c_sort.c"
+  "${FLATCC_ROOT}/src/compiler/codegen_c_builder.c"
+  "${FLATCC_ROOT}/src/compiler/codegen_c_verifier.c"
+  "${FLATCC_ROOT}/src/compiler/codegen_c_sorter.c"
+  "${FLATCC_ROOT}/src/compiler/codegen_c_json_parser.c"
+  "${FLATCC_ROOT}/src/compiler/codegen_c_json_printer.c"
+  "${FLATCC_ROOT}/src/runtime/builder.c"
+  "${FLATCC_ROOT}/src/runtime/emitter.c"
+  "${FLATCC_ROOT}/src/runtime/refmap.c"
+)
+target_include_directories(iree-flatcc-cli SYSTEM
+  PUBLIC
+    "${FLATCC_ROOT}/external"
+    "${FLATCC_ROOT}/include"
+    "${FLATCC_ROOT}/config"
+)
+install(
+  TARGETS iree-flatcc-cli
+  COMPONENT iree-flatcc-cli
+  RUNTIME DESTINATION bin
+  BUNDLE DESTINATION bin
+)
diff --git a/samples/static_library/CMakeLists.txt b/samples/static_library/CMakeLists.txt
index 5ae9f07..39ad0a6 100644
--- a/samples/static_library/CMakeLists.txt
+++ b/samples/static_library/CMakeLists.txt
@@ -10,9 +10,6 @@
   return()
 endif()
 
-# Set iree-compile binary.
-set(_COMPILE_TOOL_EXECUTABLE $<TARGET_FILE:iree-compile>)
-
 ## Example with VM bytecode module.
 # Setup args for iree-compile.
 set(_COMPILE_ARGS)
@@ -31,8 +28,8 @@
     ${CMAKE_CURRENT_BINARY_DIR}/simple_mul.h
     ${CMAKE_CURRENT_BINARY_DIR}/simple_mul.o
     ${CMAKE_CURRENT_BINARY_DIR}/simple_mul.vmfb
-  COMMAND ${_COMPILE_TOOL_EXECUTABLE} ${_COMPILE_ARGS}
-  DEPENDS ${_COMPILE_TOOL_EXECUTABLE} "simple_mul.mlir"
+  COMMAND iree-compile ${_COMPILE_ARGS}
+  DEPENDS iree-compile "simple_mul.mlir"
 )
 
 # Tell cmake about the simple_mul library so it will link it.
@@ -94,7 +91,6 @@
     "hostonly"
 )
 
-
 if(NOT (IREE_OUTPUT_FORMAT_C OR DEFINED IREE_HOST_BINARY_ROOT))
   return()
 endif()
@@ -118,8 +114,8 @@
     ${CMAKE_CURRENT_BINARY_DIR}/simple_mul_c_module.h
     ${CMAKE_CURRENT_BINARY_DIR}/simple_mul_c_module.o
     ${CMAKE_CURRENT_BINARY_DIR}/simple_mul_emitc.h
-  COMMAND ${_COMPILE_TOOL_EXECUTABLE} ${_COMPILE_ARGS}
-  DEPENDS ${_COMPILE_TOOL_EXECUTABLE} "simple_mul.mlir"
+  COMMAND iree-compile ${_COMPILE_ARGS}
+  DEPENDS iree-compile "simple_mul.mlir"
 )
 
 # TODO(marbre): Cleanup custom targets and libraries.
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
index 82f188e..4d7cad6 100644
--- a/tools/CMakeLists.txt
+++ b/tools/CMakeLists.txt
@@ -11,34 +11,34 @@
 # directory. Library targets and header files should be placed in the
 # appropriate subtree, e.g. `compiler/src/iree/compiler/Tools/`.
 #
-# Programs with a dependency on the compiler are not designed to run on device
-# platforms (e.g. Android), so they are tagged "hostonly".
+# Compiler tools are designed to run on host platforms (Linux, macOS, Windows),
+# so they are only built when IREE_BUILD_COMPILER is set and are tagged
+# "hostonly". When cross-compiling (or generally wanting to use already-built
+# compiler tools), set the IREE_HOST_BINARY_ROOT CMake option.
 #
 # This file does not use bazel_to_cmake because of special logic throughout.
 
+# Write some important CMake options to a file for convenient use from scripts.
+configure_file(build_config_template.txt.in build_config.txt)
+
+iree_add_all_subdirs()
+
+# TODO(scotttodd): Should this be checking IREE_BUILD_COMPILER?
+#   Maybe we should disallow setting both at the same time, since it's
+#   ambigious which should be used
+if(IREE_HOST_BINARY_ROOT AND NOT IREE_BUILD_COMPILER)
+  iree_import_binary(NAME iree-tblgen)
+  iree_import_binary(NAME iree-compile)
+  iree_import_binary(NAME iree-opt)
+  iree_import_binary(NAME iree-run-mlir)
+endif()
+
 # TODO(#6353): Tools has thread dependencies in gtest, benchmark, yaml, etc.
 # This should be split between runtime/compiler with optional threading support.
 if(NOT IREE_ENABLE_THREADING)
   return()
 endif()
 
-# Write some important CMake options to a file for convenient use from scripts.
-configure_file(build_config_template.txt.in build_config.txt)
-
-# Depending on which target backends are enabled, we may or may not have built
-# LLD.
-if(IREE_LLD_TARGET)
-  # lld install - required by the compiler to link codegen executables.
-  install(
-    TARGETS lld
-    COMPONENT Compiler
-    RUNTIME DESTINATION bin
-  )
-endif()
-
-add_subdirectory(android)
-add_subdirectory(test)
-
 iree_cc_binary(
   NAME
     iree-benchmark-module
@@ -172,6 +172,16 @@
 )
 
 if(IREE_BUILD_COMPILER)
+  # If a target backend that requires LLD to link codegen executables is
+  # enabled, install the target.
+  if(IREE_LLD_TARGET)
+    install(
+      TARGETS lld
+      COMPONENT Compiler
+      RUNTIME DESTINATION bin
+    )
+  endif()
+
   iree_cc_binary(
     NAME
       iree-tblgen
@@ -282,4 +292,9 @@
     DESTINATION "tests/bin"
     COMPONENT Tests
   )
-endif(IREE_BUILD_COMPILER)
+elseif(NOT DEFINED IREE_HOST_BINARY_ROOT)
+  message(STATUS
+      "*Not* building or importing IREE's compiler tools.\n   "
+      "Set IREE_BUILD_COMPILER to build them or IREE_HOST_BINARY_ROOT to "
+      "import them.")
+endif()
diff --git a/tools/android/CMakeLists.txt b/tools/android/CMakeLists.txt
index b7c992a..1033e14 100644
--- a/tools/android/CMakeLists.txt
+++ b/tools/android/CMakeLists.txt
@@ -5,4 +5,3 @@
 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
 
 iree_add_all_subdirs()
-
diff --git a/tools/test/CMakeLists.txt b/tools/test/CMakeLists.txt
index dcb3087..a82b794 100644
--- a/tools/test/CMakeLists.txt
+++ b/tools/test/CMakeLists.txt
@@ -54,7 +54,8 @@
 
 ### BAZEL_TO_CMAKE_PRESERVES_ALL_CONTENT_BELOW_THIS_LINE ###
 
-if((IREE_TARGET_BACKEND_VMVX OR DEFINED IREE_HOST_BINARY_ROOT) AND
+if(IREE_BUILD_TESTS AND
+   (IREE_TARGET_BACKEND_VMVX OR DEFINED IREE_HOST_BINARY_ROOT) AND
    IREE_HAL_DRIVER_LOCAL_SYNC)
   iree_bytecode_module(
     NAME