Author: Mehdi Amini
Date: 2026-10-06T09:57:31Z
New Revision: 94eb65e222efccf36ac36aec948d1979186bdcef

URL: 
https://github.com/llvm/llvm-project/commit/94eb65e222efccf36ac36aec948d1979186bdcef
DIFF: 
https://github.com/llvm/llvm-project/commit/94eb65e222efccf36ac36aec948d1979186bdcef.diff

LOG: [CMake] Make find_package(Clang) load MLIR when ClangIR uses a full MLIR 
(#228366)

With CLANG_ENABLE_CIR=ON, Clang's exported libraries link MLIR. When
MLIR is listed in LLVM_ENABLE_PROJECTS it installs its own package and
owns every target exported from mlir/. ClangTargets.cmake references
those targets without defining them, and ClangConfig.cmake never loaded
the MLIR package, so an external find_package(Clang) failed with:

```
  The following imported targets are referenced, but are missing:
  MLIRIR MLIRPass MLIRAnalysis ... MLIRSupport MLIRTransforms ...
```

unless the consumer happened to call find_package(MLIR) first.

When MLIR is enabled only implicitly as a ClangIR dependency
(LLVM_DEPENDENCY_ONLY_PROJECTS), no MLIR package exists and Clang
already promotes the reachable MLIR closure into its own exports, so
that mode worked. The two modes therefore need different plumbing but
should give consumers the same contract: find_package(Clang) is
self-sufficient, and where a full MLIR package exists,
find_package(MLIR) still yields the complete SDK regardless of the order
in which the two are found.

Clang side
----------
ClangConfig.cmake now loads the associated MLIR package before including
ClangTargets.cmake, but only when CIR is enabled and MLIR is not a
dependency-only project. This mirrors what FlangConfig.cmake already
does for its Clang and MLIR dependencies. Details that matter:

* The lookup is optional and QUIET rather than REQUIRED. Whether MLIR is
actually needed depends on which export sets were installed: with
LLVM_DISTRIBUTION_COMPONENTS or LLVM_DISTRIBUTIONS a package may ship
only targets that do not link MLIR (e.g. shared libclang plus the CMake
configs) and no MLIR package at all. Such a package imported fine before
and must keep doing so. The installed targets file is the source of
truth: it checks that every target it references is defined and
otherwise marks Clang as not found, which honors the consumer's
REQUIRED/QUIET. The config only appends a hint to that message
explaining that this Clang was built with ClangIR and where it looked
for MLIR.
* CMake >= 4.1 lets a consumer set CMAKE_FIND_REQUIRED to make every
find_package() REQUIRED. find_package(Clang) unsets it while loading the
config, but a direct include(ClangConfig), which the packaging
explicitly supports, does not. The variable is cleared and restored
around the MLIR lookup so it stays optional in that case too.
* Numeric LLVM_VERSION_MAJOR/MINOR/PATCH components with EXACT:
find_package(LLVM) overwrites LLVM_VERSION with the package version
string (e.g. "24.0.0git"), which find_package rejects as a version
argument. EXACT matches the existing LLVM lookup in this file and
refuses to silently import an MLIR of a different version;
FlangConfig.cmake uses an unversioned find_package(MLIR) instead.
* The lookup sits inside the existing LLVM_OMIT_EXPORTS_FROM_CONFIG
guard. LLVMConfig.cmake skips its exports in that mode while
MLIRConfig.cmake has no such guard, so loading MLIR there would fail on
missing LLVM targets.
* The MLIR package dir is derived from MLIR_INSTALL_PACKAGE_DIR relative
to CLANG_INSTALL_PREFIX for the install tree, so relocated installs and
custom package directories work. MLIR is configured before Clang
(llvm/tools/CMakeLists.txt), so that variable is already available.

When MLIR is a full project, Clang deliberately keeps the MLIR-owned
targets out of ClangTargets.cmake and relies on the lookup above instead
of bundling them the way the dependency-only mode does. Bundling them
would create two export files defining the same targets, which fails
when MLIR is found first and silently truncates the MLIR package when
Clang is found first (MLIRConfig's `if(NOT TARGET MLIRSupport)` guard
then skips the whole MLIRTargets.cmake include). The MLIRCIR* libraries
are add_clang_library targets and remain in Clang's export in both
modes.

MLIR side
---------
The build-tree MLIRConfig.cmake of a dependency-only MLIR used to be an
empty shell that reported MLIR_FOUND=1 with no targets. It now sets
MLIR_FOUND to FALSE with a MLIR_NOT_FOUND_MESSAGE explaining that this
build has MLIR only as a dependency of another project. CMake then
applies the caller's REQUIRED/QUIET handling, so an optional lookup
proceeds with MLIR_FOUND=0 and a REQUIRED one fails with that message
instead of a missing-target error later. The installed config is only
generated for a fully enabled MLIR and is unchanged.

This is a visible behavior change: a consumer that relied on the old
half-package from a CIR-only build tree, for instance to pick up
MLIR_TABLEGEN_EXE or MLIR_INCLUDE_DIRS without any targets, now sees a
REQUIRED lookup fail. Such a build never provided a usable MLIR SDK, so
reporting not-found is the honest answer.

Consumer-visible side effect
----------------------------
find_package(Clang) against a CIR build with full MLIR now also loads
the MLIR package into the consumer's project: MLIR_DIR in the cache, the
MLIR_* variables and global properties, and the mlir-tablegen-targets /
mlir-headers custom targets. This matches what find_package(Flang)
already does for its Clang and MLIR dependencies. Both changes are noted
in llvm/docs/ReleaseNotes.md under "Changes to building LLVM".

Not covered
-----------
Pointing MLIR_DIR at a different MLIR installation of the same version
is accepted by CMake's search order (MLIR_DIR wins over HINTS) and is
not rejected here. That is the same contract Clang has with LLVM_DIR
today; nothing in this change claims to verify producer identity.

Testing
-------
Configure-only LLVM producers (`clang;mlir` and `clang` alone, CIR on),
plus a consumer project that asserts clangCIR, MLIRCIR, MLIRSupport,
MLIRIR and LLVMSupport exist, checks whether MLIRAsyncDialect is
present, and verifies every entry of MLIR_EXPORTED_TARGETS exists when
MLIR_FOUND.

Explicit MLIR, build tree:

* Clang alone, Clang then MLIR, MLIR then Clang: all pass with the full
MLIR target set.
* LLVM_OMIT_EXPORTS_FROM_CONFIG=ON: no targets imported, no error.
* Optional find_package(Clang) with the MLIR lookup disabled:
Clang_FOUND=0, no error.

Explicit MLIR, cmake-exports components installed and the prefix
relocated:

* The same five cases as above pass.
* find_package(Clang REQUIRED) with the MLIR package unavailable fails
with the targets-file message plus the appended ClangIR/MLIR hint.

Explicit MLIR, LLVM_DISTRIBUTION_COMPONENTS limited to libclang, two
LLVM libraries and the CMake configs, installed without MLIR:

* find_package(Clang REQUIRED) succeeds and imports libclang with no
MLIR targets. The same package with a REQUIRED MLIR lookup in the config
fails, which is the regression this avoids.
* include(ClangConfig) via CMAKE_MODULE_PATH with CMAKE_FIND_REQUIRED=ON
succeeds and leaves CMAKE_FIND_REQUIRED set afterwards. Without the
clear/restore it fails with "This package is considered required because
the CMAKE_FIND_REQUIRED variable has been enabled".
* find_package(Clang) with CMAKE_FIND_REQUIRED=ON against the full build
tree passes.

Implicit MLIR, build tree:

* Clang alone passes, with MLIRAsyncDialect absent as expected.
* Optional find_package(MLIR) yields MLIR_FOUND=0.
* find_package(MLIR REQUIRED) fails with the new not-found message.

Assisted-by: Codex
Assisted-by: Claude

Co-authored-by: Claude Fable 5.1 <[email protected]>

Added: 
    

Modified: 
    clang/cmake/modules/CMakeLists.txt
    clang/cmake/modules/ClangConfig.cmake.in
    llvm/docs/ReleaseNotes.md
    mlir/cmake/modules/CMakeLists.txt
    mlir/cmake/modules/MLIRConfig.cmake.in

Removed: 
    


################################################################################
diff  --git a/clang/cmake/modules/CMakeLists.txt 
b/clang/cmake/modules/CMakeLists.txt
index 36f31e33cf60d..275b32feb9a47 100644
--- a/clang/cmake/modules/CMakeLists.txt
+++ b/clang/cmake/modules/CMakeLists.txt
@@ -27,9 +27,43 @@ if(NOT "clang" IN_LIST LLVM_DEPENDENCY_ONLY_PROJECTS)
   export(TARGETS ${CLANG_EXPORTS} FILE 
${clang_cmake_builddir}/ClangTargets.cmake)
 endif()
 
+# ClangIR links MLIR. How that dependency reaches a find_package(Clang)
+# consumer depends on how MLIR was enabled in this build:
+#
+# - MLIR listed in LLVM_ENABLE_PROJECTS: MLIR installs its own full package and
+#   is the sole owner of its exported targets. ClangTargets.cmake references
+#   them without defining them, so ClangConfig.cmake must load the MLIR package
+#   first. Re-exporting them from Clang would create two owners for the same
+#   targets and break find_package(MLIR) in one import order or the other.
+#
+# - MLIR enabled implicitly as a ClangIR dependency, i.e. listed in
+#   LLVM_DEPENDENCY_ONLY_PROJECTS: no MLIR package is installed.
+#   clang/CMakeLists.txt promotes the reachable MLIR closure into
+#   ClangTargets.cmake instead, so there is nothing to find.
+#
+# The lookup in ClangConfig.cmake is optional: with 
LLVM_DISTRIBUTION_COMPONENTS
+# or LLVM_DISTRIBUTIONS, an installed package may contain only export sets
+# that do not link MLIR, and the installed targets file itself reports any
+# MLIR target it references but cannot find.
+#
+# Flang loads its Clang and MLIR dependencies the same way, with a REQUIRED
+# lookup; Clang's is optional for the distribution reason above.
+if(CLANG_ENABLE_CIR AND NOT "mlir" IN_LIST LLVM_DEPENDENCY_ONLY_PROJECTS)
+  set(CLANG_CONFIG_FIND_MLIR ON)
+  # llvm/tools/CMakeLists.txt configures MLIR before Clang, so MLIR's package
+  # directory variables are already set here.
+  set(clang_config_mlir_cmake_builddir "${llvm_builddir}/cmake/mlir")
+  extend_path(clang_config_mlir_cmake_installdir "\${CLANG_INSTALL_PREFIX}" 
"${MLIR_INSTALL_PACKAGE_DIR}")
+else()
+  set(CLANG_CONFIG_FIND_MLIR OFF)
+  set(clang_config_mlir_cmake_builddir "")
+  set(clang_config_mlir_cmake_installdir "")
+endif()
+
 # Generate ClangConfig.cmake for the build tree.
 set(CLANG_CONFIG_CMAKE_DIR "${clang_cmake_builddir}")
 set(CLANG_CONFIG_LLVM_CMAKE_DIR "${llvm_cmake_builddir}")
+set(CLANG_CONFIG_MLIR_CMAKE_DIR "${clang_config_mlir_cmake_builddir}")
 if("clang" IN_LIST LLVM_DEPENDENCY_ONLY_PROJECTS)
   # Clang is enabled only to satisfy another project's dependency, so its
   # targets file is not exported above. Leave the include out of the config
@@ -53,6 +87,7 @@ configure_file(
   @ONLY)
 set(CLANG_CONFIG_CMAKE_DIR)
 set(CLANG_CONFIG_LLVM_CMAKE_DIR)
+set(CLANG_CONFIG_MLIR_CMAKE_DIR)
 
 # For compatibility with projects that include(ClangConfig)
 # via CMAKE_MODULE_PATH, place API modules next to it.
@@ -69,6 +104,7 @@ file(COPY .
 find_prefix_from_config(CLANG_CONFIG_CODE CLANG_INSTALL_PREFIX 
"${CLANG_INSTALL_PACKAGE_DIR}")
 extend_path(CLANG_CONFIG_CMAKE_DIR "\${CLANG_INSTALL_PREFIX}" 
"${CLANG_INSTALL_PACKAGE_DIR}")
 extend_path(CLANG_CONFIG_LLVM_CMAKE_DIR "\${CLANG_INSTALL_PREFIX}" 
"${LLVM_INSTALL_PACKAGE_DIR}")
+set(CLANG_CONFIG_MLIR_CMAKE_DIR "${clang_config_mlir_cmake_installdir}")
 get_config_exports_includes(Clang CLANG_CONFIG_INCLUDE_EXPORTS)
 extend_path(base_includedir "\${CLANG_INSTALL_PREFIX}" 
"${CMAKE_INSTALL_INCLUDEDIR}")
 set(CLANG_CONFIG_INCLUDE_DIRS

diff  --git a/clang/cmake/modules/ClangConfig.cmake.in 
b/clang/cmake/modules/ClangConfig.cmake.in
index fd0ff76f1e883..694ef0e391c89 100644
--- a/clang/cmake/modules/ClangConfig.cmake.in
+++ b/clang/cmake/modules/ClangConfig.cmake.in
@@ -16,7 +16,60 @@ set(CLANG_DEFAULT_UNWINDLIB "@CLANG_DEFAULT_UNWINDLIB@")
 # Provide all our library targets to users. Skip when
 # LLVM_OMIT_EXPORTS_FROM_CONFIG flag is set.
 if(NOT LLVM_OMIT_EXPORTS_FROM_CONFIG)
+  # When ClangIR is enabled and MLIR was built as a project in its own right,
+  # Clang's exported targets reference MLIR targets that are owned by the MLIR
+  # package rather than re-exported here. Load that package first so the
+  # references in ClangTargets.cmake resolve. This stays inside the
+  # LLVM_OMIT_EXPORTS_FROM_CONFIG guard because MLIRTargets.cmake in turn
+  # references LLVM targets, which are not imported in that mode.
+  #
+  # When MLIR is only an implicit dependency of ClangIR, no MLIR package is
+  # installed; the reachable MLIR targets are then part of ClangTargets.cmake
+  # itself and this block is skipped.
+  #
+  # The lookup is deliberately optional and quiet. Whether MLIR is actually
+  # needed depends on which export sets were installed: a distribution
+  # (LLVM_DISTRIBUTION_COMPONENTS / LLVM_DISTRIBUTIONS) may ship only targets
+  # that do not link MLIR, in which case no MLIR package exists and none is
+  # required. The targets file below is the source of truth: it verifies
+  # that every target it references is defined and otherwise marks Clang as
+  # not found, honoring the consumer's REQUIRED/QUIET. We only add a hint
+  # about MLIR to that message.
+  #
+  # The numeric version components are used because find_package(LLVM)
+  # above overwrites LLVM_VERSION with the package version string
+  # (e.g. "24.0.0git"), which find_package rejects as a version argument.
+  if(@CLANG_CONFIG_FIND_MLIR@)
+    # CMake >= 4.1 lets a consumer set CMAKE_FIND_REQUIRED to make every
+    # find_package() REQUIRED. find_package(Clang) unsets it while loading
+    # this file, but include(ClangConfig) does not, so clear and restore it
+    # around the lookup to keep MLIR optional in that case too.
+    if(DEFINED CMAKE_FIND_REQUIRED)
+      set(_clang_saved_find_required "${CMAKE_FIND_REQUIRED}")
+    endif()
+    set(CMAKE_FIND_REQUIRED OFF)
+    find_package(MLIR
+      ${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH}
+      EXACT CONFIG QUIET HINTS "@CLANG_CONFIG_MLIR_CMAKE_DIR@")
+    if(DEFINED _clang_saved_find_required)
+      set(CMAKE_FIND_REQUIRED "${_clang_saved_find_required}")
+      unset(_clang_saved_find_required)
+    else()
+      unset(CMAKE_FIND_REQUIRED)
+    endif()
+  endif()
   @CLANG_CONFIG_INCLUDE_EXPORTS@
+  if(@CLANG_CONFIG_FIND_MLIR@ AND NOT MLIR_FOUND AND CMAKE_FIND_PACKAGE_NAME
+     AND DEFINED ${CMAKE_FIND_PACKAGE_NAME}_FOUND
+     AND NOT ${CMAKE_FIND_PACKAGE_NAME}_FOUND)
+    string(APPEND ${CMAKE_FIND_PACKAGE_NAME}_NOT_FOUND_MESSAGE
+      "\nThis Clang was built with ClangIR, whose libraries depend on MLIR, "
+      "but no MLIR "
+      "${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.${LLVM_VERSION_PATCH} "
+      "package was found (searched \"@CLANG_CONFIG_MLIR_CMAKE_DIR@\" and the "
+      "usual find_package locations). Install the MLIR CMake package from the "
+      "same build or point MLIR_DIR at it.")
+  endif()
 endif()
 
 # By creating clang-tablegen-targets here, subprojects that depend on Clang's

diff  --git a/llvm/docs/ReleaseNotes.md b/llvm/docs/ReleaseNotes.md
index 7c09b303442a3..4e2882de82a02 100644
--- a/llvm/docs/ReleaseNotes.md
+++ b/llvm/docs/ReleaseNotes.md
@@ -198,6 +198,16 @@ Makes programs 10x faster by doing Special New Thing.
   enabling Clang or MLIR retains the project's complete build, test, and
   install behavior.
 
+* With `CLANG_ENABLE_CIR=ON` and `mlir` in `LLVM_ENABLE_PROJECTS`,
+  `find_package(Clang)` now looks for the associated MLIR CMake package
+  before importing Clang's targets, so consumers no longer need to call
+  `find_package(MLIR)` themselves and can do so in either order.
+  As a consequence the consumer's project also sees the `MLIR_*` variables and
+  targets, as it already does for Flang. When MLIR is only an implicit ClangIR
+  dependency, its build-tree `MLIRConfig.cmake` now reports the package as not
+  found with an explanatory message instead of succeeding with no targets;
+  such a build never provided a usable MLIR SDK.
+
 * LLVM's documentation has largely been rewritten from [reStructuredText] to
   Markdown, and our Sphinx documentation build now has a hard dependency on the
   [`myst-parser` package]. Vendors packaging LLVM will need to install

diff  --git a/mlir/cmake/modules/CMakeLists.txt 
b/mlir/cmake/modules/CMakeLists.txt
index e5b079dbecedf..bf32bc3019535 100644
--- a/mlir/cmake/modules/CMakeLists.txt
+++ b/mlir/cmake/modules/CMakeLists.txt
@@ -54,12 +54,24 @@ set(MLIR_CONFIG_CMAKE_DIR "${mlir_cmake_builddir}")
 set(MLIR_CONFIG_LLVM_CMAKE_DIR "${llvm_cmake_builddir}")
 if("mlir" IN_LIST LLVM_DEPENDENCY_ONLY_PROJECTS)
   # MLIR is enabled only to satisfy another project's dependency, so its
-  # targets file is not exported above. Leave the include out of the config
-  # rather than referring to a file that is never generated, which would make
-  # every find_package(MLIR) against this build tree fail.
+  # targets file is not exported above and this build tree does not provide
+  # a usable MLIR SDK. Make find_package(MLIR) against it report the package
+  # as not found with an explanation, rather than succeeding with no targets.
+  # The consumer project (e.g. Clang with ClangIR) carries the MLIR targets
+  # it needs in its own exports. Setting MLIR_FOUND to false lets CMake apply
+  # the caller's REQUIRED/QUIET handling.
   set(MLIR_CONFIG_INCLUDE_EXPORTS "")
+  string(CONCAT MLIR_CONFIG_DEPENDENCY_ONLY_CODE
+    "set(MLIR_FOUND FALSE)\n"
+    "string(CONCAT MLIR_NOT_FOUND_MESSAGE\n"
+    "  \"This LLVM build enabled MLIR only as a dependency of another project 
\"\n"
+    "  \"(LLVM_DEPENDENCY_ONLY_PROJECTS); it does not provide a full MLIR SDK. 
\"\n"
+    "  \"The MLIR targets required by that project are exported with it. \"\n"
+    "  \"To obtain a complete MLIR package, add 'mlir' to 
LLVM_ENABLE_PROJECTS.\")\n"
+    "return()")
 else()
   set(MLIR_CONFIG_INCLUDE_EXPORTS 
"include(\"\${MLIR_CMAKE_DIR}/MLIRTargets.cmake\")")
+  set(MLIR_CONFIG_DEPENDENCY_ONLY_CODE "")
 endif()
 set(MLIR_CONFIG_INCLUDE_DIRS
   "${MLIR_SOURCE_DIR}/include"
@@ -86,10 +98,12 @@ configure_file(
   @ONLY)
 
 # Reset the variables which are invalid for the installed version of
-# MLIRConfig.cmake.
+# MLIRConfig.cmake. The installed config is only generated and installed for
+# a fully enabled MLIR, so it never carries the dependency-only code.
 set(MLIR_CONFIG_CMAKE_DIR)
 set(MLIR_CONFIG_LLVM_CMAKE_DIR)
 set(MLIR_CONFIG_INCLUDE_DIRS)
+set(MLIR_CONFIG_DEPENDENCY_ONLY_CODE "")
 
 # For compatibility with projects that add '<build>/lib/cmake/mlir' to
 # their CMAKE_MODULE_PATH, place API modules next to it.

diff  --git a/mlir/cmake/modules/MLIRConfig.cmake.in 
b/mlir/cmake/modules/MLIRConfig.cmake.in
index fd04a64ce625b..4f672a1607105 100644
--- a/mlir/cmake/modules/MLIRConfig.cmake.in
+++ b/mlir/cmake/modules/MLIRConfig.cmake.in
@@ -2,6 +2,12 @@
 
 @MLIR_CONFIG_CODE@
 
+# Non-empty only in the build tree of a configuration where MLIR was enabled
+# solely as a dependency of another project (LLVM_DEPENDENCY_ONLY_PROJECTS).
+# Such a build has no MLIRTargets.cmake and must not be mistaken for a full
+# MLIR SDK, so the code below reports the package as not found.
+@MLIR_CONFIG_DEPENDENCY_ONLY_CODE@
+
 set(LLVM_VERSION 
@LLVM_VERSION_MAJOR@.@LLVM_VERSION_MINOR@.@LLVM_VERSION_PATCH@)
 find_package(LLVM ${LLVM_VERSION} EXACT REQUIRED CONFIG
              HINTS "@MLIR_CONFIG_LLVM_CMAKE_DIR@")


        
_______________________________________________
cfe-commits mailing list
[email protected]
https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to