[llvm-branch-commits] [llvm] [mlir] [MLIR][CMake] Add HEADER_LIBS and document CMake infrastructure (PR #222397)
Mehdi Amini via llvm-branch-commits
llvm-branch-commits at lists.llvm.org
Fri Sep 11 14:46:08 PDT 2026
https://github.com/joker-eph updated https://github.com/llvm/llvm-project/pull/222397
>From 8a5ff01a74f0e3409c4963c4cf3db511cfeed355 Mon Sep 17 00:00:00 2001
From: Mehdi Amini <joker.eph at gmail.com>
Date: Wed, 9 Sep 2026 06:07:45 -0700
Subject: [PATCH] [MLIR][CMake] Add HEADER_LIBS and document CMake
infrastructure
Add HEADER_LIBS as a flat list of literal library targets for generated headers
included without a link relationship. Resolve aliases and forward references,
accept imported libraries as already generated, follow nested HEADER_LIBS and
link interfaces, and reject missing, executable, utility, or generator-
expression entries with configure-time diagnostics. Represent header-only
edges in the common internal INTERFACE graph, including cyclic relationships.
Record links added by mlir_target_link_libraries for the same deferred ordering.
Add explicit, commented HEADER_LIBS edges for every audited header-only include
that is not covered by mlir-generic-headers, and keep this facility a rare
layering escape hatch.
Document dialects, interfaces, passes, PDLL, generated documentation, library
visibility, C API aggregation, tools, exports, standalone consumers, and the
generated-file model. Add a CMake fixture covering cycles, aliases, conditions,
LINK_ONLY, imported and ignored items, post-hoc links, and invalid providers.
Build each consumer independently from a clean state to verify transitive
generated-header ordering without unrelated generators masking missing edges.
Assisted-by: Codex
Assisted-by: Claude Code
Co-Authored-By: Claude Fable 5.1 <noreply at anthropic.com>
---
llvm/cmake/modules/AddLLVM.cmake | 5 +-
mlir/cmake/modules/AddMLIR.cmake | 111 +++++-
mlir/docs/CMakeInfrastructure.md | 343 ++++++++++++++++++
.../DefiningDialects/AttributesAndTypes.md | 2 +-
.../lib/Conversion/SPIRVCommon/CMakeLists.txt | 4 +
mlir/lib/Dialect/Arith/IR/CMakeLists.txt | 4 +
mlir/lib/Dialect/Func/IR/CMakeLists.txt | 4 +
mlir/lib/Dialect/LLVMIR/CMakeLists.txt | 8 +
mlir/lib/Dialect/Linalg/IR/CMakeLists.txt | 4 +
.../Dialect/Shape/Transforms/CMakeLists.txt | 4 +-
mlir/lib/Dialect/Tensor/IR/CMakeLists.txt | 4 +
mlir/lib/Dialect/Tosa/CMakeLists.txt | 4 +
mlir/lib/Rewrite/CMakeLists.txt | 11 +-
mlir/lib/Target/SPIRV/CMakeLists.txt | 4 +
mlir/test/CMake/header-dependencies.test | 192 ++++++++++
mlir/test/CMake/lit.local.cfg | 7 +
16 files changed, 704 insertions(+), 7 deletions(-)
create mode 100644 mlir/docs/CMakeInfrastructure.md
create mode 100644 mlir/test/CMake/header-dependencies.test
create mode 100644 mlir/test/CMake/lit.local.cfg
diff --git a/llvm/cmake/modules/AddLLVM.cmake b/llvm/cmake/modules/AddLLVM.cmake
index bc5f6dd46d5d2..03d90892c6a77 100644
--- a/llvm/cmake/modules/AddLLVM.cmake
+++ b/llvm/cmake/modules/AddLLVM.cmake
@@ -633,7 +633,10 @@ function(_llvm_generated_header_target output provider)
# Only a provider's interface is transitive. PRIVATE implementation links
# have already contributed to the provider's own compilation ordering.
get_property(links TARGET "${provider}" PROPERTY INTERFACE_LINK_LIBRARIES)
- foreach(item ${links})
+ # MLIR uses this property for generated headers included without a link.
+ # Both kinds of edges belong to the same header interface graph.
+ get_property(header_libraries TARGET "${provider}" PROPERTY LLVM_HEADER_LIBS)
+ foreach(item ${links} ${header_libraries})
_llvm_link_item_targets(link_targets "${item}")
foreach(link_target ${link_targets})
_llvm_generated_header_target(child_headers "${link_target}")
diff --git a/mlir/cmake/modules/AddMLIR.cmake b/mlir/cmake/modules/AddMLIR.cmake
index d3f534a02f9de..945908649a3d6 100644
--- a/mlir/cmake/modules/AddMLIR.cmake
+++ b/mlir/cmake/modules/AddMLIR.cmake
@@ -273,6 +273,103 @@ function(_check_llvm_components_usage name)
endforeach()
endfunction()
+# Normalize providers while directory-local targets are still visible.
+function(_mlir_normalize_header_libraries)
+ get_property(consumers DIRECTORY PROPERTY MLIR_HEADER_LIBS_CONSUMERS)
+ list(REMOVE_DUPLICATES consumers)
+ foreach(consumer ${consumers})
+ get_target_property(items "${consumer}" LLVM_HEADER_LIBS)
+ set(providers)
+ foreach(provider ${items})
+ # Imported targets and their aliases may be visible only in this
+ # directory. Their headers already exist, so handle them before the
+ # top-level resolver runs. Waiting until the directory is complete also
+ # supports forward declarations of these local targets.
+ if(TARGET "${provider}")
+ _llvm_resolve_target_alias(provider "${provider}")
+ get_target_property(imported "${provider}" IMPORTED)
+ get_target_property(type "${provider}" TYPE)
+ if(imported AND type MATCHES
+ "^(STATIC|SHARED|MODULE|OBJECT|INTERFACE|UNKNOWN)_LIBRARY$")
+ continue()
+ endif()
+ endif()
+ list(APPEND providers "${provider}")
+ endforeach()
+ set_property(TARGET "${consumer}" PROPERTY LLVM_HEADER_LIBS "${providers}")
+ endforeach()
+endfunction()
+
+# Record generated-header providers that are intentionally not linked. Resolve
+# them after the full project has been declared so aliases and forward target
+# references work and misspellings receive a useful diagnostic.
+function(_mlir_add_header_libraries consumer)
+ if(NOT ARGN)
+ return()
+ endif()
+ set_property(TARGET "${consumer}" APPEND PROPERTY LLVM_HEADER_LIBS ${ARGN})
+ set_property(DIRECTORY APPEND PROPERTY MLIR_HEADER_LIBS_CONSUMERS "${consumer}")
+ get_property(normalization_scheduled DIRECTORY PROPERTY
+ MLIR_HEADER_LIBS_NORMALIZATION_SCHEDULED)
+ if(NOT normalization_scheduled)
+ cmake_language(DEFER CALL _mlir_normalize_header_libraries)
+ set_property(DIRECTORY PROPERTY MLIR_HEADER_LIBS_NORMALIZATION_SCHEDULED TRUE)
+ endif()
+ set_property(GLOBAL APPEND PROPERTY MLIR_HEADER_LIBS_CONSUMERS "${consumer}")
+ get_property(scheduled GLOBAL PROPERTY MLIR_HEADER_LIBS_SCHEDULED)
+ if(NOT scheduled)
+ cmake_language(DEFER DIRECTORY "${CMAKE_SOURCE_DIR}"
+ CALL _mlir_resolve_header_libraries)
+ set_property(GLOBAL PROPERTY MLIR_HEADER_LIBS_SCHEDULED TRUE)
+ endif()
+endfunction()
+
+# Attach build-local header interfaces, which carry only generator ordering.
+# CMake follows nested and cyclic interface relationships without depending on
+# the provider libraries themselves.
+function(_mlir_resolve_header_libraries)
+ get_property(consumers GLOBAL PROPERTY MLIR_HEADER_LIBS_CONSUMERS)
+ list(REMOVE_DUPLICATES consumers)
+ foreach(consumer ${consumers})
+ get_target_property(providers "${consumer}" LLVM_HEADER_LIBS)
+ set(generated_headers)
+ foreach(provider ${providers})
+ if(provider MATCHES "\\$<")
+ message(SEND_ERROR
+ "${consumer}: HEADER_LIBS '${provider}' is not a literal target name")
+ continue()
+ endif()
+ if(NOT TARGET "${provider}")
+ message(SEND_ERROR
+ "${consumer}: HEADER_LIBS '${provider}' is not a target")
+ continue()
+ endif()
+
+ _llvm_resolve_target_alias(resolved_provider "${provider}")
+ get_target_property(type "${resolved_provider}" TYPE)
+ if(NOT type MATCHES
+ "^(STATIC|SHARED|MODULE|OBJECT|INTERFACE|UNKNOWN)_LIBRARY$")
+ message(SEND_ERROR
+ "${consumer}: HEADER_LIBS '${provider}' must name a library, not ${type}")
+ continue()
+ endif()
+
+ _llvm_generated_header_target(provider_headers "${resolved_provider}")
+ list(APPEND generated_headers ${provider_headers})
+ endforeach()
+
+ list(REMOVE_DUPLICATES generated_headers)
+ if(generated_headers)
+ set_property(TARGET "${consumer}" APPEND PROPERTY LINK_LIBRARIES
+ ${generated_headers})
+ if(TARGET "obj.${consumer}")
+ set_property(TARGET "obj.${consumer}" APPEND PROPERTY LINK_LIBRARIES
+ ${generated_headers})
+ endif()
+ endif()
+ endforeach()
+endfunction()
+
function(add_mlir_example_library name)
cmake_parse_arguments(ARG
"SHARED;DISABLE_INSTALL"
@@ -325,11 +422,16 @@ endfunction()
# are compatible with building an object library.
# STANDALONE
# Don't link against LLVMSupport.
+# HEADER_LIBS
+# A flat list of MLIR library targets whose generated headers are included
+# without linking. This is a rare escape hatch for header-only or circular
+# layering; linked libraries belong in LINK_LIBS and a library's own
+# generators belong in DEPENDS.
function(add_mlir_library name)
cmake_parse_arguments(ARG
"SHARED;INSTALL_WITH_TOOLCHAIN;EXCLUDE_FROM_LIBMLIR;DISABLE_INSTALL;ENABLE_AGGREGATION;OBJECT;STANDALONE"
""
- "ADDITIONAL_HEADERS;DEPENDS;LINK_COMPONENTS;LINK_LIBS"
+ "ADDITIONAL_HEADERS;DEPENDS;HEADER_LIBS;LINK_COMPONENTS;LINK_LIBS"
${ARGN})
_set_mlir_additional_headers_as_srcs(${ARG_ADDITIONAL_HEADERS})
@@ -401,6 +503,7 @@ function(add_mlir_library name)
# Add empty "phony" target
add_custom_target(${name})
endif()
+ _mlir_add_header_libraries(${name} ${ARG_HEADER_LIBS})
set_target_properties(${name} PROPERTIES FOLDER "MLIR/Libraries")
# Setup aggregate.
@@ -748,6 +851,12 @@ endfunction(mlir_check_all_link_libraries)
function(mlir_target_link_libraries target type)
if (TARGET obj.${target})
target_link_libraries(obj.${target} ${type} ${ARGN})
+ cmake_parse_arguments(LINK_LIBS_ARG "" "" "PUBLIC;PRIVATE;INTERFACE"
+ ${type} ${ARGN})
+ _llvm_record_link_dependencies(obj.${target}
+ ${LINK_LIBS_ARG_PUBLIC}
+ ${LINK_LIBS_ARG_PRIVATE}
+ ${LINK_LIBS_ARG_UNPARSED_ARGUMENTS})
endif()
if (MLIR_LINK_MLIR_DYLIB)
diff --git a/mlir/docs/CMakeInfrastructure.md b/mlir/docs/CMakeInfrastructure.md
new file mode 100644
index 0000000000000..a6fe8f6f87dea
--- /dev/null
+++ b/mlir/docs/CMakeInfrastructure.md
@@ -0,0 +1,343 @@
+# CMake Infrastructure
+
+[TOC]
+
+MLIR extends LLVM's CMake infrastructure with helpers for TableGen, libraries,
+dialects, interfaces, tools, installation, and aggregate libraries. This guide
+describes the MLIR-specific conventions implemented by
+[`AddMLIR.cmake`](../cmake/modules/AddMLIR.cmake). The LLVM CMake documentation
+still applies to the underlying LLVM helpers.
+
+## Loading the MLIR CMake modules
+
+The monorepo build loads the required modules. An out-of-tree project using an
+installed MLIR package normally starts with:
+
+~~~cmake
+find_package(MLIR REQUIRED CONFIG)
+
+list(APPEND CMAKE_MODULE_PATH "${MLIR_CMAKE_DIR}")
+list(APPEND CMAKE_MODULE_PATH "${LLVM_CMAKE_DIR}")
+include(TableGen)
+include(AddLLVM)
+include(AddMLIR)
+include(HandleLLVMOptions)
+~~~
+
+The project under `mlir/examples/standalone` is the canonical out-of-tree
+template. It demonstrates package discovery, generated files, libraries,
+tools, tests, and installation without depending on the monorepo build.
+
+## Source and generated-file layout
+
+Public declarations normally live below `mlir/include/mlir`, with
+implementations in the corresponding directory below `mlir/lib`. For example,
+a dialect declared in `include/mlir/Dialect/Foo/IR` is normally implemented by
+a library in `lib/Dialect/Foo/IR`.
+
+Generated target names such as `MLIRFooOpsIncGen` are build-tree details. The
+library that compiles or publishes those generated files lists the target in
+its own `DEPENDS`. Other libraries normally depend on the logical library
+through `LINK_LIBS`, not on its private generation target.
+
+Generated source files are different from published generated headers. A
+source-generation target, such as one created for sharded operations, remains
+an explicit `DEPENDS` entry of the library compiling those sources.
+
+## TableGen
+
+Set `LLVM_TARGET_DEFINITIONS`, call `mlir_tablegen` once for each output, and
+finish with the helper matching the output:
+
+| Helper | Intended output |
+| --- | --- |
+| `add_mlir_dialect` | Standard operation, type, and dialect fragments |
+| `add_mlir_dialect_tablegen_target` | Other dialect-specific headers |
+| `add_mlir_generic_tablegen_target` | Dialect-independent headers |
+| `add_public_tablegen_target` | A library-specific header or source |
+
+For example:
+
+~~~cmake
+set(LLVM_TARGET_DEFINITIONS FooPatterns.td)
+mlir_tablegen(FooPatterns.h.inc -gen-rewriters)
+add_public_tablegen_target(MLIRFooPatternsIncGen)
+~~~
+
+The library that includes `FooPatterns.h.inc` then lists
+`MLIRFooPatternsIncGen` in `DEPENDS`.
+
+`mlir-generic-headers` collects dialect-independent public generation targets,
+and every MLIR library depends on it. `mlir-headers` also includes
+dialect-specific targets. The latter is a conservative compatibility aggregate
+and should not replace precise library dependencies in new code.
+
+### Dialects
+
+The common dialect declaration is:
+
+~~~cmake
+add_mlir_dialect(FooOps foo)
+~~~
+
+This generates operation, type, and dialect declaration and definition
+fragments and creates `MLIRFooOpsIncGen`. The implementation library lists that
+target explicitly:
+
+~~~cmake
+add_mlir_dialect_library(MLIRFooDialect
+ FooDialect.cpp
+ FooOps.cpp
+
+ DEPENDS
+ MLIRFooOpsIncGen
+
+ LINK_LIBS PUBLIC
+ MLIRIR
+ )
+~~~
+
+### Operation, type, and attribute interfaces
+
+`add_mlir_interface(FooOpInterface)` emits operation-interface declaration and
+definition fragments. `add_mlir_type_interface(FooTypeInterface)` does the same
+for a type interface. Attribute interfaces and specialized interface forms use
+the corresponding `mlir_tablegen` generators followed by a dialect or generic
+TableGen target.
+
+The library implementing an interface lists its generation target in
+`DEPENDS`. A consumer links that interface library when its public or private
+C++ interface uses the generated declarations.
+
+### Passes
+
+Pass declarations use `-gen-pass-decls`; C API fragments may be emitted from
+the same `.td` file:
+
+~~~cmake
+set(LLVM_TARGET_DEFINITIONS Passes.td)
+mlir_tablegen(Passes.h.inc -gen-pass-decls -name Foo)
+mlir_tablegen(Passes.capi.h.inc -gen-pass-capi-header --prefix Foo)
+mlir_tablegen(Passes.capi.cpp.inc -gen-pass-capi-impl --prefix Foo)
+add_mlir_dialect_tablegen_target(MLIRFooPassIncGen)
+~~~
+
+The library that defines or publishes these passes keeps
+`MLIRFooPassIncGen` in `DEPENDS`. A header-only consumer uses the logical pass
+library through `HEADER_LIBS` only when linking it would be incorrect.
+
+### PDLL and generated documentation
+
+Use `add_mlir_pdll_library` to compile a PDLL source and make its generated
+output available to another target. Use `add_mlir_doc` for generated dialect,
+operation, type, attribute, interface, or pass documentation. Documentation
+targets are collected under the `mlir-doc` aggregate and are not compilation
+prerequisites unless a source target explicitly consumes their output.
+
+## Libraries
+
+`add_mlir_library` is the base helper for MLIR libraries:
+
+~~~cmake
+add_mlir_library(MLIRFooTransforms
+ FooTransforms.cpp
+
+ ADDITIONAL_HEADER_DIRS
+ ${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/Foo
+
+ DEPENDS
+ MLIRFooTransformsIncGen
+
+ LINK_COMPONENTS
+ Support
+
+ LINK_LIBS PUBLIC
+ MLIRFooDialect
+ MLIRPass
+ )
+~~~
+
+`LINK_COMPONENTS` names LLVM components. `LINK_LIBS` names CMake or MLIR
+library targets. Keeping them separate allows LLVM and MLIR to substitute their
+monolithic shared libraries correctly.
+
+Frequently used options include:
+
+| Option | Purpose |
+| --- | --- |
+| `SHARED` or `OBJECT` | Select a non-default library form |
+| `INSTALL_WITH_TOOLCHAIN` | Install with the toolchain distribution |
+| `EXCLUDE_FROM_LIBMLIR` | Exclude the library from monolithic MLIR |
+| `DISABLE_INSTALL` | Omit standard installation rules |
+| `ENABLE_AGGREGATION` | Make objects available to an MLIR aggregate |
+| `STANDALONE` | Do not add the implicit `LLVMSupport` dependency |
+| `ADDITIONAL_HEADERS` | Associate individual headers with the target |
+| `ADDITIONAL_HEADER_DIRS` | Add public headers to IDE source groups |
+| `DEPENDS` | Add the library's non-library build prerequisites |
+| `HEADER_LIBS` | Order after generated headers without linking |
+
+Prefer the wrapper describing a library's role:
+
+| Helper | Additional behavior |
+| --- | --- |
+| `add_mlir_dialect_library` | Records a target in `MLIR_DIALECT_LIBS` |
+| `add_mlir_conversion_library` | Records it in `MLIR_CONVERSION_LIBS` |
+| `add_mlir_extension_library` | Records it in `MLIR_EXTENSION_LIBS` |
+| `add_mlir_translation_library` | Records it in `MLIR_TRANSLATION_LIBS` |
+| `add_mlir_example_library` | Applies the conventions to examples |
+| `add_mlir_public_c_api_library` | Creates an aggregatable C API library |
+
+The global categories support tools and aggregates that intentionally collect
+an entire class of libraries. Ordinary libraries should list only their actual
+dependencies.
+
+### Link visibility
+
+Choose `LINK_LIBS` visibility from the C++ interface:
+
+| Declaration | Meaning |
+| --- | --- |
+| `PUBLIC A` | The target and its consumers use `A` |
+| `PRIVATE A` | Only the target implementation uses `A` |
+| `INTERFACE A` | Only consumers use `A` |
+
+Unqualified entries retain CMake's legacy signature behavior. New code should
+use explicit visibility when the distinction matters.
+
+Use `mlir_target_link_libraries` when adding links after an MLIR target was
+created, particularly for a library excluded from `libMLIR`. It applies
+`MLIR_LINK_MLIR_DYLIB` substitution and also records the generated-header
+ordering needed by an object-backed library.
+
+### Generated-header dependencies
+
+Three rules cover generated headers:
+
+1. A library lists its own TableGen and generated-source targets in `DEPENDS`.
+2. `LINK_LIBS` orders compilation after the generated-header targets reachable
+ through the linked library's interface. PUBLIC, PRIVATE, and unqualified
+ direct links are considered; INTERFACE-only links are not used by the
+ current target's compilation.
+3. `HEADER_LIBS` names a library whose generated header is included without a
+ link relationship.
+
+`HEADER_LIBS` is a rare layering escape hatch. It is appropriate when linking
+the provider would be semantically wrong or would create a circular link
+relationship. Keep it a flat list of literal target names and document the
+source-level include that requires every entry:
+
+~~~cmake
+# FooAnalysis.cpp includes mlir/Dialect/Bar/IR/BarOps.h.
+HEADER_LIBS
+MLIRBarDialect
+~~~
+
+Aliases and forward declarations are supported. Imported library targets are
+accepted because their installed generated headers already exist. Misspelled
+targets, generator expressions, executables, and utility targets are diagnosed
+at configure time. Nested and cyclic `HEADER_LIBS` relationships are safe: the
+resolver adds only generated-header leaf targets, never provider libraries.
+
+Libraries outside `add_mlir_library` do not accept `HEADER_LIBS`. A non-MLIR
+header-only consumer should retain the relevant generator in `DEPENDS`.
+
+### How ordering is modeled
+
+`add_public_tablegen_target` marks generated-header utility targets. Each
+`llvm_add_library` invocation records its exact `DEPENDS` list separately from
+the cumulative `LLVM_COMMON_DEPENDS` directory state. After the full target
+graph exists, a deferred traversal:
+
+* resolves aliases and forward references;
+* follows direct implementation links and transitive link interfaces;
+* follows nested `HEADER_LIBS` relationships;
+* conservatively extracts targets from link generator expressions; and
+* creates a build-local interface target for each header provider.
+
+These interface targets depend only on marked generators and link to other
+header interfaces. CMake handles transitive ordering and cycles in that graph.
+Consumers reference the header interfaces through their implementation links,
+without publishing them in exported link interfaces. Provider objects and
+archives are never added by this mechanism, so mutually linked static libraries
+do not form strong build cycles. Disabled generator-expression arms may
+generate extra headers. Imported libraries, paths, and flags require no
+build-tree ordering.
+
+This target-level modeling works with all supported CMake generators. Ninja's
+weaker ordering still completes a dependency's custom commands before compiling
+the consumer, while generators with stronger target ordering see only the leaf
+generator targets and do not wait for provider archives.
+
+## C API libraries and aggregation
+
+`add_mlir_public_c_api_library` creates an object-enabled MLIR library with the
+visibility definitions needed by the C API. Use `add_mlir_aggregate` to build a
+shared or static library from such components. `EMBED_LIBS` contribute their
+objects; `PUBLIC_LIBS` remain normal exported link dependencies.
+
+Aggregation metadata is exported only for libraries created with
+`ENABLE_AGGREGATION`. `MLIR_INSTALL_AGGREGATE_OBJECTS` controls whether the
+object libraries needed by an out-of-tree aggregate are installed. Imported
+components must have been installed with compatible aggregation metadata.
+
+## Tools, installation, and exports
+
+Use `add_mlir_tool` for MLIR command-line tools. It delegates to LLVM's tool
+infrastructure and participates in the normal runtime and installation layout.
+
+`add_mlir_library` installs and exports its target by default.
+`add_mlir_library_install` exposes the same rules for a non-standard library
+construction path. `DISABLE_INSTALL` suppresses those rules, while
+`INSTALL_WITH_TOOLCHAIN` includes the library when only the toolchain component
+is installed.
+
+Installed packages contain generated headers already, so imported targets do
+not contribute build-tree generator prerequisites. Exported logical library
+interfaces, rather than private `*IncGen` target names, are the contract for
+standalone consumers.
+
+## Validating dependency changes
+
+Generated-header correctness must be tested from empty generated-header state.
+An incremental build can leave files behind, and a broad aggregate can generate
+a missing header incidentally before its consumer compiles.
+
+Configure a fresh Ninja build and first build representative leaf libraries at
+normal parallelism:
+
+~~~shell
+cmake -S llvm -B <build> -G Ninja <configuration options>
+cmake --build <build> --target \
+ MLIRArmNeonDialect MLIRLinalgDialect --parallel 32
+~~~
+
+Then build the broader graph to populate compiler dependency files. Only after
+compilation succeeds, run:
+
+~~~shell
+ninja -C <build> -t missingdeps
+~~~
+
+`missingdeps` compares generated-file producers with include relationships in
+compiler depfiles. Running it before compilation cannot discover those
+includes. The clean leaf build and populated-depfile audit cover different
+failure modes.
+
+When changing the CMake infrastructure, also configure the focused CMake tests
+with Ninja and Unix Makefiles, test the oldest supported CMake release, and
+configure `mlir/examples/standalone` against an installed or build-tree MLIR
+package. Inspect generated object-order rules to confirm they contain generator
+targets and not provider archives.
+
+## Common mistakes
+
+* Depending on another library's `MLIRFooOpsIncGen` instead of linking
+ `MLIRFooDialect`.
+* Using `HEADER_LIBS` where an ordinary link accurately models the C++ layer.
+* Putting generator expressions or visibility keywords in `HEADER_LIBS`.
+* Moving dialect-specific generators into `mlir-generic-headers` to hide a
+ missing logical dependency.
+* Repairing a leaf race with the global `mlir-headers` aggregate without first
+ identifying the owning library.
+* Running `missingdeps` before compiler depfiles have been populated.
+* Validating only an incremental or broad aggregate build.
diff --git a/mlir/docs/DefiningDialects/AttributesAndTypes.md b/mlir/docs/DefiningDialects/AttributesAndTypes.md
index a339a763bd607..076b44b58f29c 100644
--- a/mlir/docs/DefiningDialects/AttributesAndTypes.md
+++ b/mlir/docs/DefiningDialects/AttributesAndTypes.md
@@ -192,7 +192,7 @@ mlir_tablegen(<Your Dialect>AttrDefs.h.inc -gen-attrdef-decls
-attrdefs-dialect=<Your Dialect>)
mlir_tablegen(<Your Dialect>AttrDefs.cpp.inc -gen-attrdef-defs
-attrdefs-dialect=<Your Dialect>)
-add_public_tablegen_target(<Your Dialect>AttrDefsIncGen)
+add_mlir_dialect_tablegen_target(<Your Dialect>AttrDefsIncGen)
```
The generated `<Your Dialect>AttrDefs.h.inc` will need to be included whereever
diff --git a/mlir/lib/Conversion/SPIRVCommon/CMakeLists.txt b/mlir/lib/Conversion/SPIRVCommon/CMakeLists.txt
index cd5a4c225efbf..7e2c426428361 100644
--- a/mlir/lib/Conversion/SPIRVCommon/CMakeLists.txt
+++ b/mlir/lib/Conversion/SPIRVCommon/CMakeLists.txt
@@ -3,4 +3,8 @@ add_mlir_conversion_library(MLIRSPIRVAttrToLLVMConversion
DEPENDS
MLIRSPIRVEnumsIncGen
+
+ # AttrToLLVMConverter.h includes the generated SPIRVEnums.h.
+ HEADER_LIBS
+ MLIRSPIRVDialect
)
diff --git a/mlir/lib/Dialect/Arith/IR/CMakeLists.txt b/mlir/lib/Dialect/Arith/IR/CMakeLists.txt
index 3423e11a7d0f0..dae42285d02b0 100644
--- a/mlir/lib/Dialect/Arith/IR/CMakeLists.txt
+++ b/mlir/lib/Dialect/Arith/IR/CMakeLists.txt
@@ -23,6 +23,10 @@ add_mlir_dialect_library(MLIRArithDialect
MLIRArithOpsIncGen
MLIRArithOpsInterfacesIncGen
+ # ArithDialect.cpp includes generated Bufferization interfaces.
+ HEADER_LIBS
+ MLIRBufferizationDialect
+
LINK_LIBS PUBLIC
MLIRCastInterfaces
MLIRDialect
diff --git a/mlir/lib/Dialect/Func/IR/CMakeLists.txt b/mlir/lib/Dialect/Func/IR/CMakeLists.txt
index 329301c6fbafd..f23335800c9be 100644
--- a/mlir/lib/Dialect/Func/IR/CMakeLists.txt
+++ b/mlir/lib/Dialect/Func/IR/CMakeLists.txt
@@ -7,6 +7,10 @@ add_mlir_dialect_library(MLIRFuncDialect
DEPENDS
MLIRFuncOpsIncGen
+ # FuncOps.cpp includes generated Bufferization interfaces.
+ HEADER_LIBS
+ MLIRBufferizationDialect
+
LINK_LIBS PUBLIC
MLIRCallInterfaces
MLIRControlFlowInterfaces
diff --git a/mlir/lib/Dialect/LLVMIR/CMakeLists.txt b/mlir/lib/Dialect/LLVMIR/CMakeLists.txt
index 50351fde8128f..df935dd681aee 100644
--- a/mlir/lib/Dialect/LLVMIR/CMakeLists.txt
+++ b/mlir/lib/Dialect/LLVMIR/CMakeLists.txt
@@ -49,6 +49,10 @@ add_mlir_dialect_library(MLIRLLVMDialect
BinaryFormat
Core
+ # LLVMAttrs.cpp includes mlir/Dialect/Ptr/IR/PtrEnums.h.
+ HEADER_LIBS
+ MLIRPtrDialect
+
LINK_LIBS PUBLIC
MLIRCallInterfaces
MLIRControlFlowInterfaces
@@ -121,6 +125,8 @@ add_mlir_dialect_library(MLIRROCDLDialect
${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/LLVMIR
DEPENDS
+ # TODO: Migrate this IncGen dependency to HEADER_LIBS after refactoring
+ # MLIRGPUCompilationInterfaces into a separate link target.
MLIRGPUCompilationAttrInterfacesIncGen
MLIRROCDLOpsIncGen
MLIRROCDLOpsShardGen
@@ -165,6 +171,8 @@ add_mlir_dialect_library(MLIRXeVMDialect
${MLIR_MAIN_INCLUDE_DIR}/mlir/Dialect/LLVMIR
DEPENDS
+ # TODO: Migrate this IncGen dependency to HEADER_LIBS after refactoring
+ # MLIRGPUCompilationInterfaces into a separate link target.
MLIRGPUCompilationAttrInterfacesIncGen
MLIRXeVMOpsIncGen
MLIRXeVMConversionsIncGen
diff --git a/mlir/lib/Dialect/Linalg/IR/CMakeLists.txt b/mlir/lib/Dialect/Linalg/IR/CMakeLists.txt
index fcba43785b6fd..960981d0e6f80 100644
--- a/mlir/lib/Dialect/Linalg/IR/CMakeLists.txt
+++ b/mlir/lib/Dialect/Linalg/IR/CMakeLists.txt
@@ -17,6 +17,10 @@ add_mlir_dialect_library(MLIRLinalgDialect
MLIRShardingInterfaceIncGen
MLIRRelayoutOpInterfaceIncGen
+ # LinalgDialect.cpp includes ShardingInterface.h for promised interfaces.
+ HEADER_LIBS
+ MLIRShardingInterface
+
LINK_LIBS PUBLIC
MLIRAffineDialect
MLIRArithDialect
diff --git a/mlir/lib/Dialect/Shape/Transforms/CMakeLists.txt b/mlir/lib/Dialect/Shape/Transforms/CMakeLists.txt
index a51c6780c2866..89755a175495e 100644
--- a/mlir/lib/Dialect/Shape/Transforms/CMakeLists.txt
+++ b/mlir/lib/Dialect/Shape/Transforms/CMakeLists.txt
@@ -9,10 +9,8 @@ add_mlir_dialect_library(MLIRShapeOpsTransforms
DEPENDS
MLIRShapeTransformsIncGen
- )
-target_link_libraries(MLIRShapeOpsTransforms
- PUBLIC
+ LINK_LIBS PUBLIC
MLIRArithDialect
MLIRBufferizationDialect
MLIRBufferizationTransforms
diff --git a/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt b/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt
index 66b73f9bead5d..493c2cd0efeb1 100644
--- a/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt
+++ b/mlir/lib/Dialect/Tensor/IR/CMakeLists.txt
@@ -17,6 +17,10 @@ add_mlir_dialect_library(MLIRTensorDialect
DEPENDS
MLIRTensorOpsIncGen
+ # TensorDialect.cpp includes TransformInterfaces.h.
+ HEADER_LIBS
+ MLIRTransformDialectInterfaces
+
LINK_LIBS PUBLIC
MLIRAffineDialect
MLIRArithDialect
diff --git a/mlir/lib/Dialect/Tosa/CMakeLists.txt b/mlir/lib/Dialect/Tosa/CMakeLists.txt
index 6404ca8fc5a56..42bfc0837d2c3 100644
--- a/mlir/lib/Dialect/Tosa/CMakeLists.txt
+++ b/mlir/lib/Dialect/Tosa/CMakeLists.txt
@@ -16,6 +16,10 @@ add_mlir_dialect_library(MLIRTosaDialect
MLIRTosaEnumsIncGen
MLIRShardingInterfaceIncGen
+ # TosaOps.cpp includes ShardingInterface.h for promised interfaces.
+ HEADER_LIBS
+ MLIRShardingInterface
+
LINK_LIBS PUBLIC
MLIRIR
MLIRDialect
diff --git a/mlir/lib/Rewrite/CMakeLists.txt b/mlir/lib/Rewrite/CMakeLists.txt
index 15b3739e4c633..eefd3808c1409 100644
--- a/mlir/lib/Rewrite/CMakeLists.txt
+++ b/mlir/lib/Rewrite/CMakeLists.txt
@@ -1,5 +1,10 @@
set(LLVM_OPTIONAL_SOURCES ByteCode.cpp)
+set(pdl_header_libraries)
+if(MLIR_ENABLE_PDL_IN_PATTERNMATCH)
+ set(pdl_header_libraries MLIRPDLInterpDialect)
+endif()
+
add_mlir_library(MLIRRewrite
FrozenRewritePatternSet.cpp
PatternApplicator.cpp
@@ -11,6 +16,11 @@ add_mlir_library(MLIRRewrite
mlir-generic-headers
MLIRConversionPassIncGen
+ # FrozenRewritePatternSet.cpp includes PDLInterp and PDL when enabled.
+ # PDLInterp's public interface also provides the PDL generated headers.
+ HEADER_LIBS
+ ${pdl_header_libraries}
+
LINK_LIBS PUBLIC
MLIRIR
MLIRSideEffectInterfaces
@@ -40,4 +50,3 @@ if(MLIR_ENABLE_PDL_IN_PATTERNMATCH)
MLIRPDLToPDLInterp
MLIRRewritePDL)
endif()
-
diff --git a/mlir/lib/Target/SPIRV/CMakeLists.txt b/mlir/lib/Target/SPIRV/CMakeLists.txt
index 1785cf92a9334..b65854e72ac31 100644
--- a/mlir/lib/Target/SPIRV/CMakeLists.txt
+++ b/mlir/lib/Target/SPIRV/CMakeLists.txt
@@ -10,6 +10,10 @@ set(LLVM_OPTIONAL_SOURCES
add_mlir_translation_library(MLIRSPIRVBinaryUtils
SPIRVBinaryUtils.cpp
+ # SPIRVBinaryUtils.h includes the generated SPIRVEnums.h.
+ HEADER_LIBS
+ MLIRSPIRVDialect
+
LINK_LIBS PUBLIC
MLIRIR
MLIRSupport
diff --git a/mlir/test/CMake/header-dependencies.test b/mlir/test/CMake/header-dependencies.test
new file mode 100644
index 0000000000000..cc1115b95e165
--- /dev/null
+++ b/mlir/test/CMake/header-dependencies.test
@@ -0,0 +1,192 @@
+# RUN: rm -rf %t
+# RUN: split-file %s %t
+# RUN: "%cmake_exe" -S %t/valid -B %t/valid-build -G "%cmake_generator" \
+# RUN: -DMLIR_DIR=%mlir_cmake_dir -DCMAKE_C_COMPILER=%host_cc \
+# RUN: -DCMAKE_CXX_COMPILER=%host_cxx 2>&1 \
+# RUN: | FileCheck %s --check-prefix=VALID
+# RUN: "%cmake_exe" --build %t/valid-build --target obj.HeaderConsumer --parallel 4
+# RUN: "%cmake_exe" --build %t/valid-build --target clean
+# RUN: "%cmake_exe" --build %t/valid-build --target obj.LinkConsumer --parallel 4
+# RUN: "%cmake_exe" --build %t/valid-build --target clean
+# RUN: "%cmake_exe" --build %t/valid-build --target SharedHeaderConsumer --parallel 4
+# RUN: not "%cmake_exe" -S %t/invalid -B %t/invalid-build \
+# RUN: -G "%cmake_generator" -DMLIR_DIR=%mlir_cmake_dir \
+# RUN: -DCMAKE_C_COMPILER=%host_cc -DCMAKE_CXX_COMPILER=%host_cxx 2>&1 \
+# RUN: | FileCheck %s --check-prefix=INVALID
+
+# VALID: -- Generated header dependency checks passed
+# INVALID-DAG: BadConsumer: HEADER_LIBS 'MisspelledProvider' is not a target
+# INVALID-DAG: BadConsumer: HEADER_LIBS 'ProviderIncGen' must name a library, not UTILITY
+# INVALID-DAG: BadConsumer: HEADER_LIBS 'NotALibrary' must name a library, not EXECUTABLE
+# INVALID-DAG: BadConsumer: HEADER_LIBS '$<$<BOOL:1>:Provider>' is not a literal target
+
+#--- common.cmake
+find_package(MLIR REQUIRED CONFIG)
+list(APPEND CMAKE_MODULE_PATH "${MLIR_CMAKE_DIR}" "${LLVM_CMAKE_DIR}")
+include(TableGen)
+include(AddLLVM)
+include(AddMLIR)
+include(HandleLLVMOptions)
+set(LLVM_RUNTIME_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/bin)
+set(LLVM_LIBRARY_OUTPUT_INTDIR ${CMAKE_BINARY_DIR}/lib)
+
+foreach(target
+ Provider Base Conditional LinkOnly Disabled Plain InterfaceOnly
+ CycleA CycleB HeaderA HeaderB HeaderBase 3rdParty)
+ set(LLVM_COMMON_DEPENDS)
+ set(output ${CMAKE_CURRENT_BINARY_DIR}/${target}.h.inc)
+ add_custom_command(OUTPUT ${output}
+ COMMAND ${CMAKE_COMMAND} -E touch ${output})
+ set(TABLEGEN_OUTPUT ${output})
+ add_public_tablegen_target(${target}IncGen)
+endforeach()
+add_custom_target(SourceGenerator COMMAND ${CMAKE_COMMAND} -E false)
+# Keep the fixture libraries from inheriting all preceding TableGen targets.
+set(LLVM_COMMON_DEPENDS)
+
+#--- valid/CMakeLists.txt
+cmake_minimum_required(VERSION 3.20)
+project(HeaderDependencies LANGUAGES C CXX)
+include(../common.cmake)
+
+add_library(Imported UNKNOWN IMPORTED)
+set_target_properties(Imported PROPERTIES
+ IMPORTED_LOCATION /absolute/libimported.a)
+add_library(ImportedAlias ALIAS Imported)
+
+# Consumers precede providers to exercise deferred forward resolution.
+add_mlir_library(LinkConsumer empty.cpp DISABLE_INSTALL
+ LINK_LIBS PlainProvider /absolute/libunused.a -lpthread ImportedAlias
+ PRIVATE ProviderAlias "$<$<BOOL:0>:DisabledProvider>"
+ PUBLIC "$<$<BOOL:1>:ConditionalProvider>" "$<LINK_ONLY:LinkOnlyProvider>"
+ "$<$<BOOL:1>:3rdPartyProvider>"
+ INTERFACE InterfaceOnlyProvider)
+add_mlir_library(CycleConsumer empty.cpp DISABLE_INSTALL
+ LINK_LIBS PRIVATE CycleA)
+add_mlir_library(HeaderConsumer empty.cpp DISABLE_INSTALL
+ HEADER_LIBS HeaderAlias)
+add_mlir_library(PostHocConsumer empty.cpp DISABLE_INSTALL)
+add_mlir_library(PostHocInterfaceConsumer empty.cpp DISABLE_INSTALL)
+add_mlir_library(SharedHeaderConsumer empty.cpp SHARED STANDALONE DISABLE_INSTALL
+ HEADER_LIBS HeaderAlias)
+target_include_directories(SharedHeaderConsumer PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+target_compile_definitions(SharedHeaderConsumer PRIVATE CHECK_HEADER_HEADERS)
+target_include_directories(obj.HeaderConsumer PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+target_compile_definitions(obj.HeaderConsumer PRIVATE CHECK_HEADER_HEADERS)
+target_include_directories(obj.LinkConsumer PRIVATE ${CMAKE_CURRENT_BINARY_DIR})
+target_compile_definitions(obj.LinkConsumer PRIVATE CHECK_LINK_HEADERS)
+
+add_mlir_library(Provider empty.cpp DISABLE_INSTALL
+ DEPENDS ProviderIncGen SourceGenerator
+ LINK_LIBS PUBLIC Base)
+add_library(ProviderAlias ALIAS Provider)
+add_mlir_library(Base empty.cpp DISABLE_INSTALL DEPENDS BaseIncGen)
+add_mlir_library(ConditionalProvider empty.cpp DISABLE_INSTALL
+ DEPENDS ConditionalIncGen)
+add_mlir_library(LinkOnlyProvider empty.cpp DISABLE_INSTALL
+ DEPENDS LinkOnlyIncGen)
+add_mlir_library(DisabledProvider empty.cpp DISABLE_INSTALL
+ DEPENDS DisabledIncGen)
+add_mlir_library(PlainProvider empty.cpp DISABLE_INSTALL
+ DEPENDS PlainIncGen)
+add_mlir_library(InterfaceOnlyProvider empty.cpp DISABLE_INSTALL
+ DEPENDS InterfaceOnlyIncGen)
+add_mlir_library(3rdPartyProvider empty.cpp DISABLE_INSTALL
+ DEPENDS 3rdPartyIncGen)
+
+# Mutual static-library links must not become strong target cycles.
+add_mlir_library(CycleA empty.cpp DISABLE_INSTALL
+ DEPENDS CycleAIncGen LINK_LIBS PRIVATE CycleB)
+add_mlir_library(CycleB empty.cpp DISABLE_INSTALL
+ DEPENDS CycleBIncGen LINK_LIBS CycleA)
+
+# HEADER_LIBS follows nested and link relationships while tolerating cycles.
+add_mlir_library(HeaderA empty.cpp DISABLE_INSTALL
+ DEPENDS HeaderAIncGen
+ HEADER_LIBS HeaderB
+ LINK_LIBS PUBLIC HeaderBase)
+add_library(HeaderAlias ALIAS HeaderA)
+add_mlir_library(HeaderB empty.cpp DISABLE_INSTALL
+ DEPENDS HeaderBIncGen HEADER_LIBS HeaderA)
+add_mlir_library(HeaderBase empty.cpp DISABLE_INSTALL
+ DEPENDS HeaderBaseIncGen)
+
+mlir_target_link_libraries(PostHocConsumer PRIVATE ProviderAlias
+ INTERFACE InterfaceOnlyProvider)
+mlir_target_link_libraries(PostHocInterfaceConsumer INTERFACE InterfaceOnlyProvider)
+add_subdirectory(local-providers)
+
+function(expect_header_libraries target expected)
+ get_target_property(actual ${target} LINK_LIBRARIES)
+ list(FILTER actual INCLUDE REGEX "^llvm\\.headers\\.")
+ list(SORT actual)
+ list(SORT expected)
+ if(NOT "${actual}" STREQUAL "${expected}")
+ message(FATAL_ERROR
+ "${target}: expected dependencies '${expected}', got '${actual}'")
+ endif()
+endfunction()
+
+function(check_dependencies)
+ # Record direct header interfaces only. The subsequent clean object builds
+ # verify that CMake follows their transitive and cyclic dependencies.
+ expect_header_libraries(obj.LinkConsumer
+ "llvm.headers.3rdPartyProvider;llvm.headers.ConditionalProvider;llvm.headers.DisabledProvider;llvm.headers.LinkOnlyProvider;llvm.headers.PlainProvider;llvm.headers.Provider")
+ expect_header_libraries(obj.CycleConsumer "llvm.headers.CycleA")
+ expect_header_libraries(obj.HeaderConsumer "llvm.headers.HeaderA")
+ expect_header_libraries(HeaderConsumer "llvm.headers.HeaderA")
+ expect_header_libraries(obj.PostHocConsumer "llvm.headers.Provider")
+ expect_header_libraries(obj.PostHocInterfaceConsumer "")
+ expect_header_libraries(obj.LocalHeaderConsumer "")
+ expect_header_libraries(SharedHeaderConsumer "llvm.headers.HeaderA")
+ message(STATUS "Generated header dependency checks passed")
+endfunction()
+# The dependency resolvers were deferred first and therefore run before this.
+cmake_language(DEFER CALL check_dependencies)
+
+#--- valid/empty.cpp
+#ifdef CHECK_HEADER_HEADERS
+#include "HeaderA.h.inc"
+#include "HeaderB.h.inc"
+#include "HeaderBase.h.inc"
+#endif
+#ifdef CHECK_LINK_HEADERS
+#include "Provider.h.inc"
+#include "Base.h.inc"
+#include "Conditional.h.inc"
+#include "LinkOnly.h.inc"
+#include "Disabled.h.inc"
+#include "Plain.h.inc"
+#include "3rdParty.h.inc"
+#endif
+void empty() {}
+
+#--- valid/Provider.td
+// Only configuration is under test.
+
+#--- valid/local-providers/CMakeLists.txt
+# Non-global imported targets and aliases are visible only in this directory.
+add_mlir_library(LocalHeaderConsumer empty.cpp DISABLE_INSTALL
+ HEADER_LIBS LocalImported LocalImportedAlias)
+add_library(LocalImported INTERFACE IMPORTED)
+add_library(LocalImportedAlias ALIAS LocalImported)
+
+#--- valid/local-providers/empty.cpp
+void localEmpty() {}
+
+#--- invalid/CMakeLists.txt
+cmake_minimum_required(VERSION 3.20)
+project(HeaderDependenciesInvalid LANGUAGES C CXX)
+include(../common.cmake)
+add_executable(NotALibrary empty.cpp)
+add_mlir_library(Provider empty.cpp DISABLE_INSTALL
+ DEPENDS ProviderIncGen)
+add_mlir_library(BadConsumer empty.cpp DISABLE_INSTALL
+ HEADER_LIBS MisspelledProvider ProviderIncGen NotALibrary
+ "$<$<BOOL:1>:Provider>")
+
+#--- invalid/empty.cpp
+void empty() {}
+
+#--- invalid/Provider.td
+// Only configuration is under test.
diff --git a/mlir/test/CMake/lit.local.cfg b/mlir/test/CMake/lit.local.cfg
new file mode 100644
index 0000000000000..af315fd5da836
--- /dev/null
+++ b/mlir/test/CMake/lit.local.cfg
@@ -0,0 +1,7 @@
+if (config.host_cmake_generator == "Xcode" or
+ config.host_cmake_generator.startswith("Visual Studio")):
+ config.unsupported = True
+
+config.substitutions.append(("%cmake_exe", config.host_cmake))
+config.substitutions.append(("%cmake_generator", config.host_cmake_generator))
+config.substitutions.append(("%mlir_cmake_dir", config.mlir_cmake_dir))
More information about the llvm-branch-commits
mailing list