1################################################################################ 2# Python modules 3# MLIR's Python modules are both directly used by the core project and are 4# available for use and embedding into external projects (in their own 5# namespace and with their own deps). In order to facilitate this, python 6# artifacts are split between declarations, which make a subset of 7# things available to be built and "add", which in line with the normal LLVM 8# nomenclature, adds libraries. 9################################################################################ 10 11# Function: declare_mlir_python_sources 12# Declares pure python sources as part of a named grouping that can be built 13# later. 14# Arguments: 15# ROOT_DIR: Directory where the python namespace begins (defaults to 16# CMAKE_CURRENT_SOURCE_DIR). For non-relocatable sources, this will 17# typically just be the root of the python source tree (current directory). 18# For relocatable sources, this will point deeper into the directory that 19# can be relocated. For generated sources, can be relative to 20# CMAKE_CURRENT_BINARY_DIR. Generated and non generated sources cannot be 21# mixed. 22# ADD_TO_PARENT: Adds this source grouping to a previously declared source 23# grouping. Source groupings form a DAG. 24# SOURCES: List of specific source files relative to ROOT_DIR to include. 25# SOURCES_GLOB: List of glob patterns relative to ROOT_DIR to include. 26function(declare_mlir_python_sources name) 27 cmake_parse_arguments(ARG 28 "" 29 "ROOT_DIR;ADD_TO_PARENT" 30 "SOURCES;SOURCES_GLOB" 31 ${ARGN}) 32 33 if(NOT ARG_ROOT_DIR) 34 set(ARG_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") 35 endif() 36 set(_install_destination "src/python/${name}") 37 38 # Process the glob. 39 set(_glob_sources) 40 if(ARG_SOURCES_GLOB) 41 set(_glob_spec ${ARG_SOURCES_GLOB}) 42 list(TRANSFORM _glob_spec PREPEND "${ARG_ROOT_DIR}/") 43 file(GLOB_RECURSE _glob_sources 44 RELATIVE "${ARG_ROOT_DIR}" 45 ${_glob_spec} 46 ) 47 list(APPEND ARG_SOURCES ${_glob_sources}) 48 endif() 49 50 # We create a custom target to carry properties and dependencies for 51 # generated sources. 52 add_library(${name} INTERFACE) 53 set_target_properties(${name} PROPERTIES 54 # Yes: Leading-lowercase property names are load bearing and the recommended 55 # way to do this: https://gitlab.kitware.com/cmake/cmake/-/issues/19261 56 EXPORT_PROPERTIES "mlir_python_SOURCES_TYPE;mlir_python_DEPENDS" 57 mlir_python_SOURCES_TYPE pure 58 mlir_python_DEPENDS "" 59 ) 60 61 # Use the interface include directories and sources on the target to carry the 62 # properties we would like to export. These support generator expressions and 63 # allow us to properly specify paths in both the local build and install scenarios. 64 # The one caveat here is that because we don't directly build against the interface 65 # library, we need to specify the INCLUDE_DIRECTORIES and SOURCES properties as well 66 # via private properties because the evaluation would happen at configuration time 67 # instead of build time. 68 # Eventually this could be done using a FILE_SET simplifying the logic below. 69 # FILE_SET is available in cmake 3.23+, so it is not an option at the moment. 70 target_include_directories(${name} INTERFACE 71 "$<BUILD_INTERFACE:${ARG_ROOT_DIR}>" 72 "$<INSTALL_INTERFACE:${_install_destination}>" 73 ) 74 set_property(TARGET ${name} PROPERTY INCLUDE_DIRECTORIES ${ARG_ROOT_DIR}) 75 76 if(ARG_SOURCES) 77 list(TRANSFORM ARG_SOURCES PREPEND "${ARG_ROOT_DIR}/" OUTPUT_VARIABLE _build_sources) 78 list(TRANSFORM ARG_SOURCES PREPEND "${_install_destination}/" OUTPUT_VARIABLE _install_sources) 79 target_sources(${name} 80 INTERFACE 81 "$<INSTALL_INTERFACE:${_install_sources}>" 82 "$<BUILD_INTERFACE:${_build_sources}>" 83 PRIVATE ${_build_sources} 84 ) 85 endif() 86 87 # Add to parent. 88 if(ARG_ADD_TO_PARENT) 89 set_property(TARGET ${ARG_ADD_TO_PARENT} APPEND PROPERTY mlir_python_DEPENDS ${name}) 90 endif() 91 92 # Install. 93 set_property(GLOBAL APPEND PROPERTY MLIR_EXPORTS ${name}) 94 if(NOT LLVM_INSTALL_TOOLCHAIN_ONLY) 95 _mlir_python_install_sources( 96 ${name} "${ARG_ROOT_DIR}" "${_install_destination}" 97 ${ARG_SOURCES} 98 ) 99 endif() 100endfunction() 101 102# Function: declare_mlir_python_extension 103# Declares a buildable python extension from C++ source files. The built 104# module is considered a python source file and included as everything else. 105# Arguments: 106# ROOT_DIR: Root directory where sources are interpreted relative to. 107# Defaults to CMAKE_CURRENT_SOURCE_DIR. 108# MODULE_NAME: Local import name of the module (i.e. "_mlir"). 109# ADD_TO_PARENT: Same as for declare_mlir_python_sources. 110# SOURCES: C++ sources making up the module. 111# PRIVATE_LINK_LIBS: List of libraries to link in privately to the module 112# regardless of how it is included in the project (generally should be 113# static libraries that can be included with hidden visibility). 114# EMBED_CAPI_LINK_LIBS: Dependent CAPI libraries that this extension depends 115# on. These will be collected for all extensions and put into an 116# aggregate dylib that is linked against. 117# PYTHON_BINDINGS_LIBRARY: Either pybind11 or nanobind. 118function(declare_mlir_python_extension name) 119 cmake_parse_arguments(ARG 120 "" 121 "ROOT_DIR;MODULE_NAME;ADD_TO_PARENT;PYTHON_BINDINGS_LIBRARY" 122 "SOURCES;PRIVATE_LINK_LIBS;EMBED_CAPI_LINK_LIBS" 123 ${ARGN}) 124 125 if(NOT ARG_ROOT_DIR) 126 set(ARG_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}") 127 endif() 128 set(_install_destination "src/python/${name}") 129 130 if(NOT ARG_PYTHON_BINDINGS_LIBRARY) 131 set(ARG_PYTHON_BINDINGS_LIBRARY "pybind11") 132 endif() 133 134 add_library(${name} INTERFACE) 135 set_target_properties(${name} PROPERTIES 136 # Yes: Leading-lowercase property names are load bearing and the recommended 137 # way to do this: https://gitlab.kitware.com/cmake/cmake/-/issues/19261 138 EXPORT_PROPERTIES "mlir_python_SOURCES_TYPE;mlir_python_EXTENSION_MODULE_NAME;mlir_python_EMBED_CAPI_LINK_LIBS;mlir_python_DEPENDS;mlir_python_BINDINGS_LIBRARY" 139 mlir_python_SOURCES_TYPE extension 140 mlir_python_EXTENSION_MODULE_NAME "${ARG_MODULE_NAME}" 141 mlir_python_EMBED_CAPI_LINK_LIBS "${ARG_EMBED_CAPI_LINK_LIBS}" 142 mlir_python_DEPENDS "" 143 mlir_python_BINDINGS_LIBRARY "${ARG_PYTHON_BINDINGS_LIBRARY}" 144 ) 145 146 # Set the interface source and link_libs properties of the target 147 # These properties support generator expressions and are automatically exported 148 list(TRANSFORM ARG_SOURCES PREPEND "${ARG_ROOT_DIR}/" OUTPUT_VARIABLE _build_sources) 149 list(TRANSFORM ARG_SOURCES PREPEND "${_install_destination}/" OUTPUT_VARIABLE _install_sources) 150 target_sources(${name} INTERFACE 151 "$<BUILD_INTERFACE:${_build_sources}>" 152 "$<INSTALL_INTERFACE:${_install_sources}>" 153 ) 154 target_link_libraries(${name} INTERFACE 155 ${ARG_PRIVATE_LINK_LIBS} 156 ) 157 158 # Add to parent. 159 if(ARG_ADD_TO_PARENT) 160 set_property(TARGET ${ARG_ADD_TO_PARENT} APPEND PROPERTY mlir_python_DEPENDS ${name}) 161 endif() 162 163 # Install. 164 set_property(GLOBAL APPEND PROPERTY MLIR_EXPORTS ${name}) 165 if(NOT LLVM_INSTALL_TOOLCHAIN_ONLY) 166 _mlir_python_install_sources( 167 ${name} "${ARG_ROOT_DIR}" "${_install_destination}" 168 ${ARG_SOURCES} 169 ) 170 endif() 171endfunction() 172 173function(_mlir_python_install_sources name source_root_dir destination) 174 foreach(source_relative_path ${ARGN}) 175 # Transform "a/b/c.py" -> "${install_prefix}/a/b" for installation. 176 get_filename_component( 177 dest_relative_dir "${source_relative_path}" DIRECTORY 178 BASE_DIR "${source_root_dir}" 179 ) 180 install( 181 FILES "${source_root_dir}/${source_relative_path}" 182 DESTINATION "${destination}/${dest_relative_dir}" 183 COMPONENT mlir-python-sources 184 ) 185 endforeach() 186 get_target_export_arg(${name} MLIR export_to_mlirtargets 187 UMBRELLA mlir-python-sources) 188 install(TARGETS ${name} 189 COMPONENT mlir-python-sources 190 ${export_to_mlirtargets} 191 ) 192endfunction() 193 194# Function: add_mlir_python_modules 195# Adds python modules to a project, building them from a list of declared 196# source groupings (see declare_mlir_python_sources and 197# declare_mlir_python_extension). One of these must be called for each 198# packaging root in use. 199# Arguments: 200# ROOT_PREFIX: The directory in the build tree to emit sources. This will 201# typically be something like ${MY_BINARY_DIR}/python_packages/foobar 202# for non-relocatable modules or a deeper directory tree for relocatable. 203# INSTALL_PREFIX: Prefix into the install tree for installing the package. 204# Typically mirrors the path above but without an absolute path. 205# DECLARED_SOURCES: List of declared source groups to include. The entire 206# DAG of source modules is included. 207# COMMON_CAPI_LINK_LIBS: List of dylibs (typically one) to make every 208# extension depend on (see mlir_python_add_common_capi_library). 209function(add_mlir_python_modules name) 210 cmake_parse_arguments(ARG 211 "" 212 "ROOT_PREFIX;INSTALL_PREFIX" 213 "COMMON_CAPI_LINK_LIBS;DECLARED_SOURCES" 214 ${ARGN}) 215 # Helper to process an individual target. 216 function(_process_target modules_target sources_target) 217 get_target_property(_source_type ${sources_target} mlir_python_SOURCES_TYPE) 218 219 if(_source_type STREQUAL "pure") 220 # Pure python sources to link into the tree. 221 set(_pure_sources_target "${modules_target}.sources.${sources_target}") 222 add_mlir_python_sources_target(${_pure_sources_target} 223 INSTALL_COMPONENT ${modules_target} 224 INSTALL_DIR ${ARG_INSTALL_PREFIX} 225 OUTPUT_DIRECTORY ${ARG_ROOT_PREFIX} 226 SOURCES_TARGETS ${sources_target} 227 ) 228 add_dependencies(${modules_target} ${_pure_sources_target}) 229 elseif(_source_type STREQUAL "extension") 230 # Native CPP extension. 231 get_target_property(_module_name ${sources_target} mlir_python_EXTENSION_MODULE_NAME) 232 get_target_property(_bindings_library ${sources_target} mlir_python_BINDINGS_LIBRARY) 233 # Transform relative source to based on root dir. 234 set(_extension_target "${modules_target}.extension.${_module_name}.dso") 235 add_mlir_python_extension(${_extension_target} "${_module_name}" 236 INSTALL_COMPONENT ${modules_target} 237 INSTALL_DIR "${ARG_INSTALL_PREFIX}/_mlir_libs" 238 OUTPUT_DIRECTORY "${ARG_ROOT_PREFIX}/_mlir_libs" 239 PYTHON_BINDINGS_LIBRARY ${_bindings_library} 240 LINK_LIBS PRIVATE 241 ${sources_target} 242 ${ARG_COMMON_CAPI_LINK_LIBS} 243 ) 244 add_dependencies(${modules_target} ${_extension_target}) 245 mlir_python_setup_extension_rpath(${_extension_target}) 246 else() 247 message(SEND_ERROR "Unrecognized source type '${_source_type}' for python source target ${sources_target}") 248 return() 249 endif() 250 endfunction() 251 252 # Build the modules target. 253 add_custom_target(${name} ALL) 254 _flatten_mlir_python_targets(_flat_targets ${ARG_DECLARED_SOURCES}) 255 foreach(sources_target ${_flat_targets}) 256 _process_target(${name} ${sources_target}) 257 endforeach() 258 259 # Create an install target. 260 if(NOT LLVM_ENABLE_IDE) 261 add_llvm_install_targets( 262 install-${name} 263 DEPENDS ${name} 264 COMPONENT ${name}) 265 endif() 266endfunction() 267 268# Function: declare_mlir_dialect_python_bindings 269# Helper to generate source groups for dialects, including both static source 270# files and a TD_FILE to generate wrappers. 271# 272# This will generate a source group named ${ADD_TO_PARENT}.${DIALECT_NAME}. 273# 274# Arguments: 275# ROOT_DIR: Same as for declare_mlir_python_sources(). 276# ADD_TO_PARENT: Same as for declare_mlir_python_sources(). Unique names 277# for the subordinate source groups are derived from this. 278# TD_FILE: Tablegen file to generate source for (relative to ROOT_DIR). 279# DIALECT_NAME: Python name of the dialect. 280# SOURCES: Same as declare_mlir_python_sources(). 281# SOURCES_GLOB: Same as declare_mlir_python_sources(). 282# DEPENDS: Additional dependency targets. 283# GEN_ENUM_BINDINGS: Generate enum bindings. 284# GEN_ENUM_BINDINGS_TD_FILE: Optional Tablegen file to generate enums for (relative to ROOT_DIR). 285# This file is where the *EnumAttrs are defined, not where the *Enums are defined. 286# **WARNING**: This arg will shortly be removed when the just-below TODO is satisfied. Use at your 287# risk. 288# 289# TODO: Right now `TD_FILE` can't be the actual dialect tablegen file, since we 290# use its path to determine where to place the generated python file. If 291# we made the output path an additional argument here we could remove the 292# need for the separate "wrapper" .td files 293function(declare_mlir_dialect_python_bindings) 294 cmake_parse_arguments(ARG 295 "GEN_ENUM_BINDINGS" 296 "ROOT_DIR;ADD_TO_PARENT;TD_FILE;DIALECT_NAME" 297 "SOURCES;SOURCES_GLOB;DEPENDS;GEN_ENUM_BINDINGS_TD_FILE" 298 ${ARGN}) 299 # Sources. 300 set(_dialect_target "${ARG_ADD_TO_PARENT}.${ARG_DIALECT_NAME}") 301 declare_mlir_python_sources(${_dialect_target} 302 ROOT_DIR "${ARG_ROOT_DIR}" 303 ADD_TO_PARENT "${ARG_ADD_TO_PARENT}" 304 SOURCES "${ARG_SOURCES}" 305 SOURCES_GLOB "${ARG_SOURCES_GLOB}" 306 ) 307 308 # Tablegen 309 if(ARG_TD_FILE) 310 set(tblgen_target "${_dialect_target}.tablegen") 311 set(td_file "${ARG_ROOT_DIR}/${ARG_TD_FILE}") 312 get_filename_component(relative_td_directory "${ARG_TD_FILE}" DIRECTORY) 313 file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${relative_td_directory}") 314 set(dialect_filename "${relative_td_directory}/_${ARG_DIALECT_NAME}_ops_gen.py") 315 set(LLVM_TARGET_DEFINITIONS ${td_file}) 316 mlir_tablegen("${dialect_filename}" 317 -gen-python-op-bindings -bind-dialect=${ARG_DIALECT_NAME} 318 DEPENDS ${ARG_DEPENDS} 319 ) 320 add_public_tablegen_target(${tblgen_target}) 321 322 set(_sources ${dialect_filename}) 323 if(ARG_GEN_ENUM_BINDINGS OR ARG_GEN_ENUM_BINDINGS_TD_FILE) 324 if(ARG_GEN_ENUM_BINDINGS_TD_FILE) 325 set(td_file "${ARG_ROOT_DIR}/${ARG_GEN_ENUM_BINDINGS_TD_FILE}") 326 set(LLVM_TARGET_DEFINITIONS ${td_file}) 327 endif() 328 set(enum_filename "${relative_td_directory}/_${ARG_DIALECT_NAME}_enum_gen.py") 329 mlir_tablegen(${enum_filename} -gen-python-enum-bindings) 330 list(APPEND _sources ${enum_filename}) 331 endif() 332 333 # Generated. 334 declare_mlir_python_sources("${_dialect_target}.ops_gen" 335 ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}" 336 ADD_TO_PARENT "${_dialect_target}" 337 SOURCES ${_sources} 338 ) 339 endif() 340endfunction() 341 342# Function: declare_mlir_dialect_extension_python_bindings 343# Helper to generate source groups for dialect extensions, including both 344# static source files and a TD_FILE to generate wrappers. 345# 346# This will generate a source group named ${ADD_TO_PARENT}.${EXTENSION_NAME}. 347# 348# Arguments: 349# ROOT_DIR: Same as for declare_mlir_python_sources(). 350# ADD_TO_PARENT: Same as for declare_mlir_python_sources(). Unique names 351# for the subordinate source groups are derived from this. 352# TD_FILE: Tablegen file to generate source for (relative to ROOT_DIR). 353# DIALECT_NAME: Python name of the dialect. 354# EXTENSION_NAME: Python name of the dialect extension. 355# SOURCES: Same as declare_mlir_python_sources(). 356# SOURCES_GLOB: Same as declare_mlir_python_sources(). 357# DEPENDS: Additional dependency targets. 358# GEN_ENUM_BINDINGS: Generate enum bindings. 359# GEN_ENUM_BINDINGS_TD_FILE: Optional Tablegen file to generate enums for (relative to ROOT_DIR). 360# This file is where the *Attrs are defined, not where the *Enums are defined. 361# **WARNING**: This arg will shortly be removed when the TODO for 362# declare_mlir_dialect_python_bindings is satisfied. Use at your risk. 363function(declare_mlir_dialect_extension_python_bindings) 364 cmake_parse_arguments(ARG 365 "GEN_ENUM_BINDINGS" 366 "ROOT_DIR;ADD_TO_PARENT;TD_FILE;DIALECT_NAME;EXTENSION_NAME" 367 "SOURCES;SOURCES_GLOB;DEPENDS;GEN_ENUM_BINDINGS_TD_FILE" 368 ${ARGN}) 369 # Source files. 370 set(_extension_target "${ARG_ADD_TO_PARENT}.${ARG_EXTENSION_NAME}") 371 declare_mlir_python_sources(${_extension_target} 372 ROOT_DIR "${ARG_ROOT_DIR}" 373 ADD_TO_PARENT "${ARG_ADD_TO_PARENT}" 374 SOURCES "${ARG_SOURCES}" 375 SOURCES_GLOB "${ARG_SOURCES_GLOB}" 376 ) 377 378 # Tablegen 379 if(ARG_TD_FILE) 380 set(tblgen_target "${ARG_ADD_TO_PARENT}.${ARG_EXTENSION_NAME}.tablegen") 381 set(td_file "${ARG_ROOT_DIR}/${ARG_TD_FILE}") 382 get_filename_component(relative_td_directory "${ARG_TD_FILE}" DIRECTORY) 383 file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${relative_td_directory}") 384 set(output_filename "${relative_td_directory}/_${ARG_EXTENSION_NAME}_ops_gen.py") 385 set(LLVM_TARGET_DEFINITIONS ${td_file}) 386 mlir_tablegen("${output_filename}" -gen-python-op-bindings 387 -bind-dialect=${ARG_DIALECT_NAME} 388 -dialect-extension=${ARG_EXTENSION_NAME}) 389 add_public_tablegen_target(${tblgen_target}) 390 if(ARG_DEPENDS) 391 add_dependencies(${tblgen_target} ${ARG_DEPENDS}) 392 endif() 393 394 set(_sources ${output_filename}) 395 if(ARG_GEN_ENUM_BINDINGS OR ARG_GEN_ENUM_BINDINGS_TD_FILE) 396 if(ARG_GEN_ENUM_BINDINGS_TD_FILE) 397 set(td_file "${ARG_ROOT_DIR}/${ARG_GEN_ENUM_BINDINGS_TD_FILE}") 398 set(LLVM_TARGET_DEFINITIONS ${td_file}) 399 endif() 400 set(enum_filename "${relative_td_directory}/_${ARG_EXTENSION_NAME}_enum_gen.py") 401 mlir_tablegen(${enum_filename} -gen-python-enum-bindings) 402 list(APPEND _sources ${enum_filename}) 403 endif() 404 405 declare_mlir_python_sources("${_extension_target}.ops_gen" 406 ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}" 407 ADD_TO_PARENT "${_extension_target}" 408 SOURCES ${_sources} 409 ) 410 endif() 411endfunction() 412 413# Function: mlir_python_setup_extension_rpath 414# Sets RPATH properties on a target, assuming that it is being output to 415# an _mlir_libs directory with all other libraries. For static linkage, 416# the RPATH will just be the origin. If linking dynamically, then the LLVM 417# library directory will be added. 418# Arguments: 419# RELATIVE_INSTALL_ROOT: If building dynamically, an RPATH entry will be 420# added to the install tree lib/ directory by first traversing this 421# path relative to the installation location. Typically a number of ".." 422# entries, one for each level of the install path. 423function(mlir_python_setup_extension_rpath target) 424 cmake_parse_arguments(ARG 425 "" 426 "RELATIVE_INSTALL_ROOT" 427 "" 428 ${ARGN}) 429 430 # RPATH handling. 431 # For the build tree, include the LLVM lib directory and the current 432 # directory for RPATH searching. For install, just the current directory 433 # (assumes that needed dependencies have been installed). 434 if(NOT APPLE AND NOT UNIX) 435 return() 436 endif() 437 438 set(_origin_prefix "\$ORIGIN") 439 if(APPLE) 440 set(_origin_prefix "@loader_path") 441 endif() 442 set_target_properties(${target} PROPERTIES 443 BUILD_WITH_INSTALL_RPATH OFF 444 BUILD_RPATH "${_origin_prefix}" 445 INSTALL_RPATH "${_origin_prefix}" 446 ) 447 448 # For static builds, that is all that is needed: all dependencies will be in 449 # the one directory. For shared builds, then we also need to add the global 450 # lib directory. This will be absolute for the build tree and relative for 451 # install. 452 # When we have access to CMake >= 3.20, there is a helper to calculate this. 453 if(BUILD_SHARED_LIBS AND ARG_RELATIVE_INSTALL_ROOT) 454 get_filename_component(_real_lib_dir "${LLVM_LIBRARY_OUTPUT_INTDIR}" REALPATH) 455 set_property(TARGET ${target} APPEND PROPERTY 456 BUILD_RPATH "${_real_lib_dir}") 457 set_property(TARGET ${target} APPEND PROPERTY 458 INSTALL_RPATH "${_origin_prefix}/${ARG_RELATIVE_INSTALL_ROOT}/lib${LLVM_LIBDIR_SUFFIX}") 459 endif() 460endfunction() 461 462# Function: add_mlir_python_common_capi_library 463# Adds a shared library which embeds dependent CAPI libraries needed to link 464# all extensions. 465# Arguments: 466# INSTALL_COMPONENT: Name of the install component. Typically same as the 467# target name passed to add_mlir_python_modules(). 468# INSTALL_DESTINATION: Prefix into the install tree in which to install the 469# library. 470# OUTPUT_DIRECTORY: Full path in the build tree in which to create the 471# library. Typically, this will be the common _mlir_libs directory where 472# all extensions are emitted. 473# RELATIVE_INSTALL_ROOT: See mlir_python_setup_extension_rpath(). 474# DECLARED_HEADERS: Source groups from which to discover headers that belong 475# to the library and should be installed with it. 476# DECLARED_SOURCES: Source groups from which to discover dependent 477# EMBED_CAPI_LINK_LIBS. 478# EMBED_LIBS: Additional libraries to embed (must be built with OBJECTS and 479# have an "obj.${name}" object library associated). 480function(add_mlir_python_common_capi_library name) 481 cmake_parse_arguments(ARG 482 "" 483 "INSTALL_COMPONENT;INSTALL_DESTINATION;OUTPUT_DIRECTORY;RELATIVE_INSTALL_ROOT" 484 "DECLARED_HEADERS;DECLARED_SOURCES;EMBED_LIBS" 485 ${ARGN}) 486 # Collect all explicit and transitive embed libs. 487 set(_embed_libs ${ARG_EMBED_LIBS}) 488 _flatten_mlir_python_targets(_all_source_targets ${ARG_DECLARED_SOURCES}) 489 foreach(t ${_all_source_targets}) 490 get_target_property(_local_embed_libs ${t} mlir_python_EMBED_CAPI_LINK_LIBS) 491 if(_local_embed_libs) 492 list(APPEND _embed_libs ${_local_embed_libs}) 493 endif() 494 endforeach() 495 list(REMOVE_DUPLICATES _embed_libs) 496 497 # Generate the aggregate .so that everything depends on. 498 add_mlir_aggregate(${name} 499 SHARED 500 DISABLE_INSTALL 501 EMBED_LIBS ${_embed_libs} 502 ) 503 504 # Process any headers associated with the library 505 _flatten_mlir_python_targets(_flat_header_targets ${ARG_DECLARED_HEADERS}) 506 set(_header_sources_target "${name}.sources") 507 add_mlir_python_sources_target(${_header_sources_target} 508 INSTALL_COMPONENT ${ARG_INSTALL_COMPONENT} 509 INSTALL_DIR "${ARG_INSTALL_DESTINATION}/include" 510 OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}/include" 511 SOURCES_TARGETS ${_flat_header_targets} 512 ) 513 add_dependencies(${name} ${_header_sources_target}) 514 515 if(WIN32) 516 set_property(TARGET ${name} PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS ON) 517 endif() 518 set_target_properties(${name} PROPERTIES 519 LIBRARY_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}" 520 BINARY_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}" 521 # Needed for windows (and don't hurt others). 522 RUNTIME_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}" 523 ARCHIVE_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}" 524 ) 525 mlir_python_setup_extension_rpath(${name} 526 RELATIVE_INSTALL_ROOT "${ARG_RELATIVE_INSTALL_ROOT}" 527 ) 528 install(TARGETS ${name} 529 COMPONENT ${ARG_INSTALL_COMPONENT} 530 LIBRARY DESTINATION "${ARG_INSTALL_DESTINATION}" 531 RUNTIME DESTINATION "${ARG_INSTALL_DESTINATION}" 532 ) 533endfunction() 534 535function(_flatten_mlir_python_targets output_var) 536 set(_flattened) 537 foreach(t ${ARGN}) 538 get_target_property(_source_type ${t} mlir_python_SOURCES_TYPE) 539 get_target_property(_depends ${t} mlir_python_DEPENDS) 540 if(_source_type) 541 list(APPEND _flattened "${t}") 542 if(_depends) 543 _flatten_mlir_python_targets(_local_flattened ${_depends}) 544 list(APPEND _flattened ${_local_flattened}) 545 endif() 546 endif() 547 endforeach() 548 list(REMOVE_DUPLICATES _flattened) 549 set(${output_var} "${_flattened}" PARENT_SCOPE) 550endfunction() 551 552# Function: add_mlir_python_sources_target 553# Adds a target corresponding to an interface target that carries source 554# information. This target is responsible for "building" the sources by 555# placing them in the correct locations in the build and install trees. 556# Arguments: 557# INSTALL_COMPONENT: Name of the install component. Typically same as the 558# target name passed to add_mlir_python_modules(). 559# INSTALL_DESTINATION: Prefix into the install tree in which to install the 560# library. 561# OUTPUT_DIRECTORY: Full path in the build tree in which to create the 562# library. Typically, this will be the common _mlir_libs directory where 563# all extensions are emitted. 564# SOURCES_TARGETS: List of interface libraries that carry source information. 565function(add_mlir_python_sources_target name) 566 cmake_parse_arguments(ARG 567 "" 568 "INSTALL_COMPONENT;INSTALL_DIR;OUTPUT_DIRECTORY" 569 "SOURCES_TARGETS" 570 ${ARGN}) 571 572 if(ARG_UNPARSED_ARGUMENTS) 573 message(FATAL_ERROR "Unhandled arguments to add_mlir_python_sources_target(${name}, ... : ${ARG_UNPARSED_ARGUMENTS}") 574 endif() 575 576 # On Windows create_symlink requires special permissions. Use copy_if_different instead. 577 if(CMAKE_HOST_WIN32) 578 set(_link_or_copy copy_if_different) 579 else() 580 set(_link_or_copy create_symlink) 581 endif() 582 583 foreach(_sources_target ${ARG_SOURCES_TARGETS}) 584 get_target_property(_src_paths ${_sources_target} SOURCES) 585 if(NOT _src_paths) 586 get_target_property(_src_paths ${_sources_target} INTERFACE_SOURCES) 587 if(NOT _src_paths) 588 break() 589 endif() 590 endif() 591 592 get_target_property(_root_dir ${_sources_target} INCLUDE_DIRECTORIES) 593 if(NOT _root_dir) 594 get_target_property(_root_dir ${_sources_target} INTERFACE_INCLUDE_DIRECTORIES) 595 endif() 596 597 # Initialize an empty list of all Python source destination paths. 598 set(all_dest_paths "") 599 foreach(_src_path ${_src_paths}) 600 file(RELATIVE_PATH _source_relative_path "${_root_dir}" "${_src_path}") 601 set(_dest_path "${ARG_OUTPUT_DIRECTORY}/${_source_relative_path}") 602 603 get_filename_component(_dest_dir "${_dest_path}" DIRECTORY) 604 file(MAKE_DIRECTORY "${_dest_dir}") 605 606 add_custom_command( 607 OUTPUT "${_dest_path}" 608 COMMENT "Copying python source ${_src_path} -> ${_dest_path}" 609 DEPENDS "${_src_path}" 610 COMMAND "${CMAKE_COMMAND}" -E ${_link_or_copy} 611 "${_src_path}" "${_dest_path}" 612 ) 613 614 # Track the symlink or copy command output. 615 list(APPEND all_dest_paths "${_dest_path}") 616 617 if(ARG_INSTALL_DIR) 618 # We have to install each file individually because we need to preserve 619 # the relative directory structure in the install destination. 620 # As an example, ${_source_relative_path} may be dialects/math.py 621 # which would be transformed to ${ARG_INSTALL_DIR}/dialects 622 # here. This could be moved outside of the loop and cleaned up 623 # if we used FILE_SETS (introduced in CMake 3.23). 624 get_filename_component(_install_destination "${ARG_INSTALL_DIR}/${_source_relative_path}" DIRECTORY) 625 install( 626 FILES ${_src_path} 627 DESTINATION "${_install_destination}" 628 COMPONENT ${ARG_INSTALL_COMPONENT} 629 ) 630 endif() 631 endforeach() 632 endforeach() 633 634 # Create a new custom target that depends on all symlinked or copied sources. 635 add_custom_target("${name}" DEPENDS ${all_dest_paths}) 636endfunction() 637 638################################################################################ 639# Build python extension 640################################################################################ 641function(add_mlir_python_extension libname extname) 642 cmake_parse_arguments(ARG 643 "" 644 "INSTALL_COMPONENT;INSTALL_DIR;OUTPUT_DIRECTORY;PYTHON_BINDINGS_LIBRARY" 645 "SOURCES;LINK_LIBS" 646 ${ARGN}) 647 if(ARG_UNPARSED_ARGUMENTS) 648 message(FATAL_ERROR "Unhandled arguments to add_mlir_python_extension(${libname}, ... : ${ARG_UNPARSED_ARGUMENTS}") 649 endif() 650 651 # The extension itself must be compiled with RTTI and exceptions enabled. 652 # Also, some warning classes triggered by pybind11 are disabled. 653 set(eh_rtti_enable) 654 if (MSVC) 655 set(eh_rtti_enable /EHsc /GR) 656 elseif(LLVM_COMPILER_IS_GCC_COMPATIBLE OR CLANG_CL) 657 set(eh_rtti_enable -frtti -fexceptions) 658 endif () 659 660 # The actual extension library produces a shared-object or DLL and has 661 # sources that must be compiled in accordance with pybind11 needs (RTTI and 662 # exceptions). 663 if(NOT DEFINED ARG_PYTHON_BINDINGS_LIBRARY OR ARG_PYTHON_BINDINGS_LIBRARY STREQUAL "pybind11") 664 pybind11_add_module(${libname} 665 ${ARG_SOURCES} 666 ) 667 elseif(ARG_PYTHON_BINDINGS_LIBRARY STREQUAL "nanobind") 668 nanobind_add_module(${libname} 669 NB_DOMAIN ${MLIR_BINDINGS_PYTHON_NB_DOMAIN} 670 FREE_THREADED 671 ${ARG_SOURCES} 672 ) 673 674 if (NOT MLIR_DISABLE_CONFIGURE_PYTHON_DEV_PACKAGES 675 AND (LLVM_COMPILER_IS_GCC_COMPATIBLE OR CLANG_CL)) 676 # Avoid some warnings from upstream nanobind. 677 # If a superproject set MLIR_DISABLE_CONFIGURE_PYTHON_DEV_PACKAGES, let 678 # the super project handle compile options as it wishes. 679 set(nanobind_target "nanobind-static") 680 if (NOT TARGET ${nanobind_target}) 681 # Get correct nanobind target name: nanobind-static-ft or something else 682 # It is set by nanobind_add_module function according to the passed options 683 get_property(all_targets DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} PROPERTY BUILDSYSTEM_TARGETS) 684 685 # Iterate over the list of targets 686 foreach(target ${all_targets}) 687 # Check if the target name matches the given string 688 if("${target}" MATCHES "nanobind-") 689 set(nanobind_target "${target}") 690 endif() 691 endforeach() 692 693 if (NOT TARGET ${nanobind_target}) 694 message(FATAL_ERROR "Could not find nanobind target to set compile options to") 695 endif() 696 endif() 697 target_compile_options(${nanobind_target} 698 PRIVATE 699 -Wno-cast-qual 700 -Wno-zero-length-array 701 -Wno-nested-anon-types 702 -Wno-c++98-compat-extra-semi 703 -Wno-covered-switch-default 704 ${eh_rtti_enable} 705 ) 706 endif() 707 708 if(APPLE) 709 # NanobindAdaptors.h uses PyClassMethod_New to build `pure_subclass`es but nanobind 710 # doesn't declare this API as undefined in its linker flags. So we need to declare it as such 711 # for downstream users that do not do something like `-undefined dynamic_lookup`. 712 set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-U -Wl,_PyClassMethod_New") 713 endif() 714 endif() 715 716 target_compile_options(${libname} PRIVATE ${eh_rtti_enable}) 717 718 # Configure the output to match python expectations. 719 set_target_properties( 720 ${libname} PROPERTIES 721 LIBRARY_OUTPUT_DIRECTORY ${ARG_OUTPUT_DIRECTORY} 722 OUTPUT_NAME "${extname}" 723 NO_SONAME ON 724 ) 725 726 if(WIN32) 727 # Need to also set the RUNTIME_OUTPUT_DIRECTORY on Windows in order to 728 # control where the .dll gets written. 729 set_target_properties( 730 ${libname} PROPERTIES 731 RUNTIME_OUTPUT_DIRECTORY ${ARG_OUTPUT_DIRECTORY} 732 ARCHIVE_OUTPUT_DIRECTORY ${ARG_OUTPUT_DIRECTORY} 733 ) 734 endif() 735 736 target_link_libraries(${libname} 737 PRIVATE 738 ${ARG_LINK_LIBS} 739 ) 740 741 target_link_options(${libname} 742 PRIVATE 743 # On Linux, disable re-export of any static linked libraries that 744 # came through. 745 $<$<PLATFORM_ID:Linux>:LINKER:--exclude-libs,ALL> 746 ) 747 748 if(WIN32) 749 # On Windows, pyconfig.h (and by extension python.h) hardcode the version of the 750 # python library which will be used for linkage depending on the flavor of the build. 751 # pybind11 has a workaround which depends on the definition of Py_DEBUG (if Py_DEBUG 752 # is not passed in as a compile definition, pybind11 undefs _DEBUG when including 753 # python.h, so that the release python library would be used). 754 # Since mlir uses pybind11, we can leverage their workaround by never directly 755 # pyconfig.h or python.h and instead relying on the pybind11 headers to include the 756 # necessary python headers. This results in mlir always linking against the 757 # release python library via the (undocumented) cmake property Python3_LIBRARY_RELEASE. 758 target_link_libraries(${libname} PRIVATE ${Python3_LIBRARY_RELEASE}) 759 endif() 760 761 ################################################################################ 762 # Install 763 ################################################################################ 764 if(ARG_INSTALL_DIR) 765 install(TARGETS ${libname} 766 COMPONENT ${ARG_INSTALL_COMPONENT} 767 LIBRARY DESTINATION ${ARG_INSTALL_DIR} 768 ARCHIVE DESTINATION ${ARG_INSTALL_DIR} 769 # NOTE: Even on DLL-platforms, extensions go in the lib directory tree. 770 RUNTIME DESTINATION ${ARG_INSTALL_DIR} 771 ) 772 endif() 773endfunction() 774