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.
117function(declare_mlir_python_extension name)
118  cmake_parse_arguments(ARG
119    ""
120    "ROOT_DIR;MODULE_NAME;ADD_TO_PARENT"
121    "SOURCES;PRIVATE_LINK_LIBS;EMBED_CAPI_LINK_LIBS"
122    ${ARGN})
123
124  if(NOT ARG_ROOT_DIR)
125    set(ARG_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
126  endif()
127  set(_install_destination "src/python/${name}")
128
129  add_library(${name} INTERFACE)
130  set_target_properties(${name} PROPERTIES
131    # Yes: Leading-lowercase property names are load bearing and the recommended
132    # way to do this: https://gitlab.kitware.com/cmake/cmake/-/issues/19261
133    EXPORT_PROPERTIES "mlir_python_SOURCES_TYPE;mlir_python_EXTENSION_MODULE_NAME;mlir_python_EMBED_CAPI_LINK_LIBS;mlir_python_DEPENDS"
134    mlir_python_SOURCES_TYPE extension
135    mlir_python_EXTENSION_MODULE_NAME "${ARG_MODULE_NAME}"
136    mlir_python_EMBED_CAPI_LINK_LIBS "${ARG_EMBED_CAPI_LINK_LIBS}"
137    mlir_python_DEPENDS ""
138  )
139
140  # Set the interface source and link_libs properties of the target
141  # These properties support generator expressions and are automatically exported
142  list(TRANSFORM ARG_SOURCES PREPEND "${ARG_ROOT_DIR}/" OUTPUT_VARIABLE _build_sources)
143  list(TRANSFORM ARG_SOURCES PREPEND "${_install_destination}/" OUTPUT_VARIABLE _install_sources)
144  target_sources(${name} INTERFACE
145    "$<BUILD_INTERFACE:${_build_sources}>"
146    "$<INSTALL_INTERFACE:${_install_sources}>"
147  )
148  target_link_libraries(${name} INTERFACE
149    ${ARG_PRIVATE_LINK_LIBS}
150  )
151
152  # Add to parent.
153  if(ARG_ADD_TO_PARENT)
154    set_property(TARGET ${ARG_ADD_TO_PARENT} APPEND PROPERTY mlir_python_DEPENDS ${name})
155  endif()
156
157  # Install.
158  set_property(GLOBAL APPEND PROPERTY MLIR_EXPORTS ${name})
159  if(NOT LLVM_INSTALL_TOOLCHAIN_ONLY)
160    _mlir_python_install_sources(
161      ${name} "${ARG_ROOT_DIR}" "${_install_destination}"
162      ${ARG_SOURCES}
163    )
164  endif()
165endfunction()
166
167function(_mlir_python_install_sources name source_root_dir destination)
168  foreach(source_relative_path ${ARGN})
169    # Transform "a/b/c.py" -> "${install_prefix}/a/b" for installation.
170    get_filename_component(
171      dest_relative_dir "${source_relative_path}" DIRECTORY
172      BASE_DIR "${source_root_dir}"
173    )
174    install(
175      FILES "${source_root_dir}/${source_relative_path}"
176      DESTINATION "${destination}/${dest_relative_dir}"
177      COMPONENT "${name}"
178    )
179  endforeach()
180  get_target_export_arg(${name} MLIR export_to_mlirtargets UMBRELLA mlir-libraries)
181  install(TARGETS ${name}
182    COMPONENT ${name}
183    ${export_to_mlirtargets}
184  )
185endfunction()
186
187# Function: add_mlir_python_modules
188# Adds python modules to a project, building them from a list of declared
189# source groupings (see declare_mlir_python_sources and
190# declare_mlir_python_extension). One of these must be called for each
191# packaging root in use.
192# Arguments:
193#   ROOT_PREFIX: The directory in the build tree to emit sources. This will
194#     typically be something like ${MY_BINARY_DIR}/python_packages/foobar
195#     for non-relocatable modules or a deeper directory tree for relocatable.
196#   INSTALL_PREFIX: Prefix into the install tree for installing the package.
197#     Typically mirrors the path above but without an absolute path.
198#   DECLARED_SOURCES: List of declared source groups to include. The entire
199#     DAG of source modules is included.
200#   COMMON_CAPI_LINK_LIBS: List of dylibs (typically one) to make every
201#     extension depend on (see mlir_python_add_common_capi_library).
202function(add_mlir_python_modules name)
203  cmake_parse_arguments(ARG
204    ""
205    "ROOT_PREFIX;INSTALL_PREFIX;COMMON_CAPI_LINK_LIBS"
206    "DECLARED_SOURCES"
207    ${ARGN})
208  # Helper to process an individual target.
209  function(_process_target modules_target sources_target)
210    get_target_property(_source_type ${sources_target} mlir_python_SOURCES_TYPE)
211
212    if(_source_type STREQUAL "pure")
213      # Pure python sources to link into the tree.
214      set(_pure_sources_target "${modules_target}.sources.${sources_target}")
215      add_mlir_python_sources_target(${_pure_sources_target}
216        INSTALL_COMPONENT ${modules_target}
217        INSTALL_DIR ${ARG_INSTALL_PREFIX}
218        OUTPUT_DIRECTORY ${ARG_ROOT_PREFIX}
219        SOURCES_TARGETS ${sources_target}
220      )
221      add_dependencies(${modules_target} ${_pure_sources_target})
222    elseif(_source_type STREQUAL "extension")
223      # Native CPP extension.
224      get_target_property(_module_name ${sources_target} mlir_python_EXTENSION_MODULE_NAME)
225      # Transform relative source to based on root dir.
226      set(_extension_target "${modules_target}.extension.${_module_name}.dso")
227      add_mlir_python_extension(${_extension_target} "${_module_name}"
228        INSTALL_COMPONENT ${modules_target}
229        INSTALL_DIR "${ARG_INSTALL_PREFIX}/_mlir_libs"
230        OUTPUT_DIRECTORY "${ARG_ROOT_PREFIX}/_mlir_libs"
231        LINK_LIBS PRIVATE
232          ${sources_target}
233          ${ARG_COMMON_CAPI_LINK_LIBS}
234      )
235      add_dependencies(${modules_target} ${_extension_target})
236      mlir_python_setup_extension_rpath(${_extension_target})
237    else()
238      message(SEND_ERROR "Unrecognized source type '${_source_type}' for python source target ${sources_target}")
239      return()
240    endif()
241  endfunction()
242
243  # Build the modules target.
244  add_custom_target(${name} ALL)
245  _flatten_mlir_python_targets(_flat_targets ${ARG_DECLARED_SOURCES})
246  foreach(sources_target ${_flat_targets})
247    _process_target(${name} ${sources_target})
248  endforeach()
249
250  # Create an install target.
251  if(NOT LLVM_ENABLE_IDE)
252    add_llvm_install_targets(
253      install-${name}
254      DEPENDS ${name}
255      COMPONENT ${name})
256  endif()
257endfunction()
258
259# Function: declare_mlir_dialect_python_bindings
260# Helper to generate source groups for dialects, including both static source
261# files and a TD_FILE to generate wrappers.
262#
263# This will generate a source group named ${ADD_TO_PARENT}.${DIALECT_NAME}.
264#
265# Arguments:
266#   ROOT_DIR: Same as for declare_mlir_python_sources().
267#   ADD_TO_PARENT: Same as for declare_mlir_python_sources(). Unique names
268#     for the subordinate source groups are derived from this.
269#   TD_FILE: Tablegen file to generate source for (relative to ROOT_DIR).
270#   DIALECT_NAME: Python name of the dialect.
271#   SOURCES: Same as declare_mlir_python_sources().
272#   SOURCES_GLOB: Same as declare_mlir_python_sources().
273#   DEPENDS: Additional dependency targets.
274function(declare_mlir_dialect_python_bindings)
275  cmake_parse_arguments(ARG
276    ""
277    "ROOT_DIR;ADD_TO_PARENT;TD_FILE;DIALECT_NAME"
278    "SOURCES;SOURCES_GLOB;DEPENDS"
279    ${ARGN})
280  # Sources.
281  set(_dialect_target "${ARG_ADD_TO_PARENT}.${ARG_DIALECT_NAME}")
282  declare_mlir_python_sources(${_dialect_target}
283    ROOT_DIR "${ARG_ROOT_DIR}"
284    ADD_TO_PARENT "${ARG_ADD_TO_PARENT}"
285    SOURCES "${ARG_SOURCES}"
286    SOURCES_GLOB "${ARG_SOURCES_GLOB}"
287  )
288
289  # Tablegen
290  if(ARG_TD_FILE)
291    set(tblgen_target "${_dialect_target}.tablegen")
292    set(td_file "${ARG_ROOT_DIR}/${ARG_TD_FILE}")
293    get_filename_component(relative_td_directory "${ARG_TD_FILE}" DIRECTORY)
294    file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${relative_td_directory}")
295    set(dialect_filename "${relative_td_directory}/_${ARG_DIALECT_NAME}_ops_gen.py")
296    set(LLVM_TARGET_DEFINITIONS ${td_file})
297    mlir_tablegen("${dialect_filename}"
298      -gen-python-op-bindings -bind-dialect=${ARG_DIALECT_NAME}
299      DEPENDS ${ARG_DEPENDS}
300    )
301    add_public_tablegen_target(${tblgen_target})
302
303    # Generated.
304    declare_mlir_python_sources("${_dialect_target}.ops_gen"
305      ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}"
306      ADD_TO_PARENT "${_dialect_target}"
307      SOURCES "${dialect_filename}"
308    )
309  endif()
310endfunction()
311
312# Function: declare_mlir_dialect_extension_python_bindings
313# Helper to generate source groups for dialect extensions, including both
314# static source files and a TD_FILE to generate wrappers.
315#
316# This will generate a source group named ${ADD_TO_PARENT}.${EXTENSION_NAME}.
317#
318# Arguments:
319#   ROOT_DIR: Same as for declare_mlir_python_sources().
320#   ADD_TO_PARENT: Same as for declare_mlir_python_sources(). Unique names
321#     for the subordinate source groups are derived from this.
322#   TD_FILE: Tablegen file to generate source for (relative to ROOT_DIR).
323#   DIALECT_NAME: Python name of the dialect.
324#   EXTENSION_NAME: Python name of the dialect extension.
325#   SOURCES: Same as declare_mlir_python_sources().
326#   SOURCES_GLOB: Same as declare_mlir_python_sources().
327#   DEPENDS: Additional dependency targets.
328function(declare_mlir_dialect_extension_python_bindings)
329  cmake_parse_arguments(ARG
330    ""
331    "ROOT_DIR;ADD_TO_PARENT;TD_FILE;DIALECT_NAME;EXTENSION_NAME"
332    "SOURCES;SOURCES_GLOB;DEPENDS"
333    ${ARGN})
334  # Source files.
335  set(_extension_target "${ARG_ADD_TO_PARENT}.${ARG_EXTENSION_NAME}")
336  declare_mlir_python_sources(${_extension_target}
337    ROOT_DIR "${ARG_ROOT_DIR}"
338    ADD_TO_PARENT "${ARG_ADD_TO_PARENT}"
339    SOURCES "${ARG_SOURCES}"
340    SOURCES_GLOB "${ARG_SOURCES_GLOB}"
341  )
342
343  # Tablegen
344  if(ARG_TD_FILE)
345    set(tblgen_target "${ARG_ADD_TO_PARENT}.${ARG_EXTENSION_NAME}.tablegen")
346    set(td_file "${ARG_ROOT_DIR}/${ARG_TD_FILE}")
347    get_filename_component(relative_td_directory "${ARG_TD_FILE}" DIRECTORY)
348    file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${relative_td_directory}")
349    set(output_filename "${relative_td_directory}/_${ARG_EXTENSION_NAME}_ops_gen.py")
350    set(LLVM_TARGET_DEFINITIONS ${td_file})
351    mlir_tablegen("${output_filename}" -gen-python-op-bindings
352                  -bind-dialect=${ARG_DIALECT_NAME}
353                  -dialect-extension=${ARG_EXTENSION_NAME})
354    add_public_tablegen_target(${tblgen_target})
355    if(ARG_DEPENDS)
356      add_dependencies(${tblgen_target} ${ARG_DEPENDS})
357    endif()
358
359    declare_mlir_python_sources("${_extension_target}.ops_gen"
360      ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}"
361      ADD_TO_PARENT "${_extension_target}"
362      SOURCES "${output_filename}"
363    )
364  endif()
365endfunction()
366
367# Function: mlir_python_setup_extension_rpath
368# Sets RPATH properties on a target, assuming that it is being output to
369# an _mlir_libs directory with all other libraries. For static linkage,
370# the RPATH will just be the origin. If linking dynamically, then the LLVM
371# library directory will be added.
372# Arguments:
373#   RELATIVE_INSTALL_ROOT: If building dynamically, an RPATH entry will be
374#     added to the install tree lib/ directory by first traversing this
375#     path relative to the installation location. Typically a number of ".."
376#     entries, one for each level of the install path.
377function(mlir_python_setup_extension_rpath target)
378  cmake_parse_arguments(ARG
379    ""
380    "RELATIVE_INSTALL_ROOT"
381    ""
382    ${ARGN})
383
384  # RPATH handling.
385  # For the build tree, include the LLVM lib directory and the current
386  # directory for RPATH searching. For install, just the current directory
387  # (assumes that needed dependencies have been installed).
388  if(NOT APPLE AND NOT UNIX)
389    return()
390  endif()
391
392  set(_origin_prefix "\$ORIGIN")
393  if(APPLE)
394    set(_origin_prefix "@loader_path")
395  endif()
396  set_target_properties(${target} PROPERTIES
397    BUILD_WITH_INSTALL_RPATH OFF
398    BUILD_RPATH "${_origin_prefix}"
399    INSTALL_RPATH "${_origin_prefix}"
400  )
401
402  # For static builds, that is all that is needed: all dependencies will be in
403  # the one directory. For shared builds, then we also need to add the global
404  # lib directory. This will be absolute for the build tree and relative for
405  # install.
406  # When we have access to CMake >= 3.20, there is a helper to calculate this.
407  if(BUILD_SHARED_LIBS AND ARG_RELATIVE_INSTALL_ROOT)
408    get_filename_component(_real_lib_dir "${LLVM_LIBRARY_OUTPUT_INTDIR}" REALPATH)
409    set_property(TARGET ${target} APPEND PROPERTY
410      BUILD_RPATH "${_real_lib_dir}")
411    set_property(TARGET ${target} APPEND PROPERTY
412      INSTALL_RPATH "${_origin_prefix}/${ARG_RELATIVE_INSTALL_ROOT}/lib${LLVM_LIBDIR_SUFFIX}")
413  endif()
414endfunction()
415
416# Function: add_mlir_python_common_capi_library
417# Adds a shared library which embeds dependent CAPI libraries needed to link
418# all extensions.
419# Arguments:
420#   INSTALL_COMPONENT: Name of the install component. Typically same as the
421#     target name passed to add_mlir_python_modules().
422#   INSTALL_DESTINATION: Prefix into the install tree in which to install the
423#     library.
424#   OUTPUT_DIRECTORY: Full path in the build tree in which to create the
425#     library. Typically, this will be the common _mlir_libs directory where
426#     all extensions are emitted.
427#   RELATIVE_INSTALL_ROOT: See mlir_python_setup_extension_rpath().
428#   DECLARED_HEADERS: Source groups from which to discover headers that belong
429#     to the library and should be installed with it.
430#   DECLARED_SOURCES: Source groups from which to discover dependent
431#     EMBED_CAPI_LINK_LIBS.
432#   EMBED_LIBS: Additional libraries to embed (must be built with OBJECTS and
433#     have an "obj.${name}" object library associated).
434function(add_mlir_python_common_capi_library name)
435  cmake_parse_arguments(ARG
436    ""
437    "INSTALL_COMPONENT;INSTALL_DESTINATION;OUTPUT_DIRECTORY;RELATIVE_INSTALL_ROOT"
438    "DECLARED_HEADERS;DECLARED_SOURCES;EMBED_LIBS"
439    ${ARGN})
440  # Collect all explicit and transitive embed libs.
441  set(_embed_libs ${ARG_EMBED_LIBS})
442  _flatten_mlir_python_targets(_all_source_targets ${ARG_DECLARED_SOURCES})
443  foreach(t ${_all_source_targets})
444    get_target_property(_local_embed_libs ${t} mlir_python_EMBED_CAPI_LINK_LIBS)
445    if(_local_embed_libs)
446      list(APPEND _embed_libs ${_local_embed_libs})
447    endif()
448  endforeach()
449  list(REMOVE_DUPLICATES _embed_libs)
450
451  # Generate the aggregate .so that everything depends on.
452  add_mlir_aggregate(${name}
453    SHARED
454    DISABLE_INSTALL
455    EMBED_LIBS ${_embed_libs}
456  )
457
458  # Process any headers associated with the library
459  _flatten_mlir_python_targets(_flat_header_targets ${ARG_DECLARED_HEADERS})
460  set(_header_sources_target "${name}.sources")
461  add_mlir_python_sources_target(${_header_sources_target}
462    INSTALL_COMPONENT ${ARG_INSTALL_COMPONENT}
463    INSTALL_DIR "${ARG_INSTALL_DESTINATION}/include"
464    OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}/include"
465    SOURCES_TARGETS ${_flat_header_targets}
466  )
467  add_dependencies(${name} ${_header_sources_target})
468
469  if(MSVC)
470    set_property(TARGET ${name} PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS ON)
471  endif()
472  set_target_properties(${name} PROPERTIES
473    LIBRARY_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}"
474    BINARY_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}"
475    # Needed for windows (and don't hurt others).
476    RUNTIME_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}"
477    ARCHIVE_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}"
478  )
479  mlir_python_setup_extension_rpath(${name}
480    RELATIVE_INSTALL_ROOT "${ARG_RELATIVE_INSTALL_ROOT}"
481  )
482  install(TARGETS ${name}
483    COMPONENT ${ARG_INSTALL_COMPONENT}
484    LIBRARY DESTINATION "${ARG_INSTALL_DESTINATION}"
485    RUNTIME DESTINATION "${ARG_INSTALL_DESTINATION}"
486  )
487endfunction()
488
489function(_flatten_mlir_python_targets output_var)
490  set(_flattened)
491  foreach(t ${ARGN})
492    get_target_property(_source_type ${t} mlir_python_SOURCES_TYPE)
493    get_target_property(_depends ${t} mlir_python_DEPENDS)
494    if(_source_type)
495      list(APPEND _flattened "${t}")
496      if(_depends)
497        _flatten_mlir_python_targets(_local_flattened ${_depends})
498        list(APPEND _flattened ${_local_flattened})
499      endif()
500    endif()
501  endforeach()
502  list(REMOVE_DUPLICATES _flattened)
503  set(${output_var} "${_flattened}" PARENT_SCOPE)
504endfunction()
505
506# Function: add_mlir_python_sources_target
507# Adds a target corresponding to an interface target that carries source
508# information. This target is responsible for "building" the sources by
509# placing them in the correct locations in the build and install trees.
510# Arguments:
511#   INSTALL_COMPONENT: Name of the install component. Typically same as the
512#     target name passed to add_mlir_python_modules().
513#   INSTALL_DESTINATION: Prefix into the install tree in which to install the
514#     library.
515#   OUTPUT_DIRECTORY: Full path in the build tree in which to create the
516#     library. Typically, this will be the common _mlir_libs directory where
517#     all extensions are emitted.
518#   SOURCES_TARGETS: List of interface libraries that carry source information.
519function(add_mlir_python_sources_target name)
520  cmake_parse_arguments(ARG
521  ""
522  "INSTALL_COMPONENT;INSTALL_DIR;OUTPUT_DIRECTORY"
523  "SOURCES_TARGETS"
524  ${ARGN})
525
526  if(ARG_UNPARSED_ARGUMENTS)
527    message(FATAL_ERROR "Unhandled arguments to add_mlir_python_sources_target(${name}, ... : ${ARG_UNPARSED_ARGUMENTS}")
528  endif()
529
530  add_custom_target(${name})
531
532  # On Windows create_symlink requires special permissions. Use copy_if_different instead.
533  if(CMAKE_HOST_WIN32)
534    set(_link_or_copy copy_if_different)
535  else()
536    set(_link_or_copy create_symlink)
537  endif()
538
539  foreach(_sources_target ${ARG_SOURCES_TARGETS})
540    add_dependencies(${name} ${_sources_target})
541
542    get_target_property(_src_paths ${_sources_target} SOURCES)
543    if(NOT _src_paths)
544      get_target_property(_src_paths ${_sources_target} INTERFACE_SOURCES)
545      if(NOT _src_paths)
546        break()
547      endif()
548    endif()
549
550    get_target_property(_root_dir ${_sources_target} INCLUDE_DIRECTORIES)
551    if(NOT _root_dir)
552      get_target_property(_root_dir ${_sources_target} INTERFACE_INCLUDE_DIRECTORIES)
553    endif()
554
555    foreach(_src_path ${_src_paths})
556      file(RELATIVE_PATH _source_relative_path "${_root_dir}" "${_src_path}")
557      set(_dest_path "${ARG_OUTPUT_DIRECTORY}/${_source_relative_path}")
558
559      get_filename_component(_dest_dir "${_dest_path}" DIRECTORY)
560      file(MAKE_DIRECTORY "${_dest_dir}")
561
562      add_custom_command(
563        TARGET ${name} PRE_BUILD
564        COMMENT "Copying python source ${_src_path} -> ${_dest_path}"
565        DEPENDS "${_src_path}"
566        BYPRODUCTS "${_dest_path}"
567        COMMAND "${CMAKE_COMMAND}" -E ${_link_or_copy}
568            "${_src_path}" "${_dest_path}"
569      )
570      if(ARG_INSTALL_DIR)
571        # We have to install each file individually because we need to preserve
572        # the relative directory structure in the install destination.
573        # As an example, ${_source_relative_path} may be dialects/math.py
574        # which would be transformed to ${ARG_INSTALL_DIR}/dialects
575        # here. This could be moved outside of the loop and cleaned up
576        # if we used FILE_SETS (introduced in CMake 3.23).
577        get_filename_component(_install_destination "${ARG_INSTALL_DIR}/${_source_relative_path}" DIRECTORY)
578        install(
579          FILES ${_src_path}
580          DESTINATION "${_install_destination}"
581          COMPONENT ${ARG_INSTALL_COMPONENT}
582        )
583      endif()
584    endforeach()
585  endforeach()
586endfunction()
587
588################################################################################
589# Build python extension
590################################################################################
591function(add_mlir_python_extension libname extname)
592  cmake_parse_arguments(ARG
593  ""
594  "INSTALL_COMPONENT;INSTALL_DIR;OUTPUT_DIRECTORY"
595  "SOURCES;LINK_LIBS"
596  ${ARGN})
597  if(ARG_UNPARSED_ARGUMENTS)
598    message(FATAL_ERROR "Unhandled arguments to add_mlir_python_extension(${libname}, ... : ${ARG_UNPARSED_ARGUMENTS}")
599  endif()
600
601  # The actual extension library produces a shared-object or DLL and has
602  # sources that must be compiled in accordance with pybind11 needs (RTTI and
603  # exceptions).
604  pybind11_add_module(${libname}
605    ${ARG_SOURCES}
606  )
607
608  # The extension itself must be compiled with RTTI and exceptions enabled.
609  # Also, some warning classes triggered by pybind11 are disabled.
610  target_compile_options(${libname} PRIVATE
611    $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
612      # Enable RTTI and exceptions.
613      -frtti -fexceptions
614    >
615    $<$<CXX_COMPILER_ID:MSVC>:
616      # Enable RTTI and exceptions.
617      /EHsc /GR>
618  )
619
620  # Configure the output to match python expectations.
621  set_target_properties(
622    ${libname} PROPERTIES
623    LIBRARY_OUTPUT_DIRECTORY ${ARG_OUTPUT_DIRECTORY}
624    OUTPUT_NAME "${extname}"
625    NO_SONAME ON
626  )
627
628  if(WIN32)
629    # Need to also set the RUNTIME_OUTPUT_DIRECTORY on Windows in order to
630    # control where the .dll gets written.
631    set_target_properties(
632      ${libname} PROPERTIES
633      RUNTIME_OUTPUT_DIRECTORY ${ARG_OUTPUT_DIRECTORY}
634      ARCHIVE_OUTPUT_DIRECTORY ${ARG_OUTPUT_DIRECTORY}
635    )
636  endif()
637
638  target_link_libraries(${libname}
639    PRIVATE
640    ${ARG_LINK_LIBS}
641  )
642
643  target_link_options(${libname}
644    PRIVATE
645      # On Linux, disable re-export of any static linked libraries that
646      # came through.
647      $<$<PLATFORM_ID:Linux>:LINKER:--exclude-libs,ALL>
648  )
649
650  if(WIN32)
651    # On Windows, pyconfig.h (and by extension python.h) hardcode the version of the
652    # python library which will be used for linkage depending on the flavor of the build.
653    # pybind11 has a workaround which depends on the definition of Py_DEBUG (if Py_DEBUG
654    # is not passed in as a compile definition, pybind11 undefs _DEBUG when including
655    # python.h, so that the release python library would be used).
656    # Since mlir uses pybind11, we can leverage their workaround by never directly
657    # pyconfig.h or python.h and instead relying on the pybind11 headers to include the
658    # necessary python headers. This results in mlir always linking against the
659    # release python library via the (undocumented) cmake property Python3_LIBRARY_RELEASE.
660    target_link_libraries(${libname} PRIVATE ${Python3_LIBRARY_RELEASE})
661  endif()
662
663  ################################################################################
664  # Install
665  ################################################################################
666  if(ARG_INSTALL_DIR)
667    install(TARGETS ${libname}
668      COMPONENT ${ARG_INSTALL_COMPONENT}
669      LIBRARY DESTINATION ${ARG_INSTALL_DIR}
670      ARCHIVE DESTINATION ${ARG_INSTALL_DIR}
671      # NOTE: Even on DLL-platforms, extensions go in the lib directory tree.
672      RUNTIME DESTINATION ${ARG_INSTALL_DIR}
673    )
674  endif()
675endfunction()
676