xref: /llvm-project/mlir/cmake/modules/AddMLIRPython.cmake (revision 1a8f49fdda5b14ccc894aacee653f19130df3a30)
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