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
