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.
26#   DEST_PREFIX: Destination prefix to prepend to files in the python
27#     package directory namespace.
28function(declare_mlir_python_sources name)
29  cmake_parse_arguments(ARG
30    ""
31    "ROOT_DIR;ADD_TO_PARENT;DEST_PREFIX"
32    "SOURCES;SOURCES_GLOB"
33    ${ARGN})
34
35  if(NOT ARG_ROOT_DIR)
36    set(ARG_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
37  endif()
38  set(_install_destination "src/python/${name}")
39
40  # Process the glob.
41  set(_glob_sources)
42  if(ARG_SOURCES_GLOB)
43    set(_glob_spec ${ARG_SOURCES_GLOB})
44    list(TRANSFORM _glob_spec PREPEND "${ARG_ROOT_DIR}/")
45    file(GLOB_RECURSE _glob_sources
46      RELATIVE "${ARG_ROOT_DIR}"
47      ${_glob_spec}
48    )
49    list(APPEND ARG_SOURCES ${_glob_sources})
50  endif()
51
52  # We create a custom target to carry properties and dependencies for
53  # generated sources.
54  add_library(${name} INTERFACE)
55  set(_file_depends "${ARG_SOURCES}")
56  list(TRANSFORM _file_depends PREPEND "${ARG_ROOT_DIR}/")
57  set_target_properties(${name} PROPERTIES
58    # Yes: Leading-lowercase property names are load bearing and the recommended
59    # way to do this: https://gitlab.kitware.com/cmake/cmake/-/issues/19261
60    # Note that ROOT_DIR and FILE_DEPENDS are not exported because they are
61    # only relevant to in-tree uses.
62    EXPORT_PROPERTIES "mlir_python_SOURCES_TYPE;mlir_python_DEST_PREFIX;mlir_python_ROOT_DIR;mlir_python_SOURCES;mlir_python_DEPENDS"
63    mlir_python_SOURCES_TYPE pure
64    mlir_python_ROOT_DIR "${ARG_ROOT_DIR}"
65    mlir_python_DEST_PREFIX "${ARG_DEST_PREFIX}"
66    mlir_python_SOURCES "${ARG_SOURCES}"
67    mlir_python_FILE_DEPENDS "${_file_depends}"
68    mlir_python_DEPENDS ""
69  )
70  # Note that an "include" directory has no meaning to such faux targets,
71  # but it is a CMake supported way to specify a directory search list in a
72  # way that works both in-tree and out. It has some super powers which are
73  # not possible to emulate with custom properties (because of the prohibition
74  # on using generator expressions in exported custom properties and the
75  # special dispensation for $<INSTALL_PREFIX>).
76  target_include_directories(${name} INTERFACE
77    "$<BUILD_INTERFACE:${ARG_ROOT_DIR}>"
78    "$<INSTALL_INTERFACE:${_install_destination}>"
79  )
80
81  # Add to parent.
82  if(ARG_ADD_TO_PARENT)
83    set_property(TARGET ${ARG_ADD_TO_PARENT} APPEND PROPERTY mlir_python_DEPENDS ${name})
84  endif()
85
86  # Install.
87  set_property(GLOBAL APPEND PROPERTY MLIR_EXPORTS ${name})
88  if(NOT LLVM_INSTALL_TOOLCHAIN_ONLY)
89    _mlir_python_install_sources(
90      ${name} "${ARG_ROOT_DIR}" "${_install_destination}"
91      ${ARG_SOURCES}
92    )
93  endif()
94endfunction()
95
96# Function: declare_mlir_python_extension
97# Declares a buildable python extension from C++ source files. The built
98# module is considered a python source file and included as everything else.
99# Arguments:
100#   ROOT_DIR: Root directory where sources are interpreted relative to.
101#     Defaults to CMAKE_CURRENT_SOURCE_DIR.
102#   MODULE_NAME: Local import name of the module (i.e. "_mlir").
103#   ADD_TO_PARENT: Same as for declare_mlir_python_sources.
104#   SOURCES: C++ sources making up the module.
105#   PRIVATE_LINK_LIBS: List of libraries to link in privately to the module
106#     regardless of how it is included in the project (generally should be
107#     static libraries that can be included with hidden visibility).
108#   EMBED_CAPI_LINK_LIBS: Dependent CAPI libraries that this extension depends
109#     on. These will be collected for all extensions and put into an
110#     aggregate dylib that is linked against.
111function(declare_mlir_python_extension name)
112  cmake_parse_arguments(ARG
113    ""
114    "ROOT_DIR;MODULE_NAME;ADD_TO_PARENT"
115    "SOURCES;PRIVATE_LINK_LIBS;EMBED_CAPI_LINK_LIBS"
116    ${ARGN})
117
118  if(NOT ARG_ROOT_DIR)
119    set(ARG_ROOT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
120  endif()
121  set(_install_destination "src/python/${name}")
122
123  add_library(${name} INTERFACE)
124  set_target_properties(${name} PROPERTIES
125    # Yes: Leading-lowercase property names are load bearing and the recommended
126    # way to do this: https://gitlab.kitware.com/cmake/cmake/-/issues/19261
127    # Note that ROOT_DIR and FILE_DEPENDS are not exported because they are
128    # only relevant to in-tree uses.
129    EXPORT_PROPERTIES "mlir_python_SOURCES_TYPE;mlir_python_ROOT_DIR;mlir_python_EXTENSION_MODULE_NAME;mlir_python_CPP_SOURCES;mlir_python_PRIVATE_LINK_LIBS;mlir_python_EMBED_CAPI_LINK_LIBS;mlir_python_DEPENDS"
130    mlir_python_SOURCES_TYPE extension
131    mlir_python_ROOT_DIR "${ARG_ROOT_DIR}"
132    mlir_python_EXTENSION_MODULE_NAME "${ARG_MODULE_NAME}"
133    mlir_python_CPP_SOURCES "${ARG_SOURCES}"
134    mlir_python_PRIVATE_LINK_LIBS "${ARG_PRIVATE_LINK_LIBS}"
135    mlir_python_EMBED_CAPI_LINK_LIBS "${ARG_EMBED_CAPI_LINK_LIBS}"
136    mlir_python_FILE_DEPENDS ""
137    mlir_python_DEPENDS ""
138  )
139  # Note that an "include" directory has no meaning to such faux targets,
140  # but it is a CMake supported way to specify an install-prefix relative
141  # directory. It has some super powers which are not possible to emulate
142  # with custom properties (because of the prohibition on using generator
143  # expressions in exported custom properties and the special dispensation
144  # for $<INSTALL_PREFIX> and $<INSTALL_INTERFACE>). On imported targets,
145  # this is used as a single value, not as a list, so it must only have one
146  # item in it.
147  target_include_directories(${name} INTERFACE
148    "$<INSTALL_INTERFACE:${_install_destination}>"
149  )
150
151  # Add to parent.
152  if(ARG_ADD_TO_PARENT)
153    set_property(TARGET ${ARG_ADD_TO_PARENT} APPEND PROPERTY mlir_python_DEPENDS ${name})
154  endif()
155
156  # Install.
157  set_property(GLOBAL APPEND PROPERTY MLIR_EXPORTS ${name})
158  if(NOT LLVM_INSTALL_TOOLCHAIN_ONLY)
159    _mlir_python_install_sources(
160      ${name} "${ARG_ROOT_DIR}" "src/python/${name}"
161      ${ARG_SOURCES}
162    )
163  endif()
164endfunction()
165
166function(_mlir_python_install_sources name source_root_dir destination)
167  foreach(source_relative_path ${ARGN})
168    # Transform "a/b/c.py" -> "${install_prefix}/a/b" for installation.
169    get_filename_component(
170      dest_relative_path "${source_relative_path}" DIRECTORY
171      BASE_DIR "${source_root_dir}"
172    )
173    install(
174      FILES "${source_root_dir}/${source_relative_path}"
175      DESTINATION "${destination}/${dest_relative_path}"
176      COMPONENT "${name}"
177    )
178  endforeach()
179  get_target_export_arg(${name} MLIR export_to_mlirtargets UMBRELLA mlir-libraries)
180  install(TARGETS ${name}
181    COMPONENT ${name}
182    ${export_to_mlirtargets}
183  )
184endfunction()
185
186# Function: add_mlir_python_modules
187# Adds python modules to a project, building them from a list of declared
188# source groupings (see declare_mlir_python_sources and
189# declare_mlir_python_extension). One of these must be called for each
190# packaging root in use.
191# Arguments:
192#   ROOT_PREFIX: The directory in the build tree to emit sources. This will
193#     typically be something like ${MY_BINARY_DIR}/python_packages/foobar
194#     for non-relocatable modules or a deeper directory tree for relocatable.
195#   INSTALL_PREFIX: Prefix into the install tree for installing the package.
196#     Typically mirrors the path above but without an absolute path.
197#   DECLARED_SOURCES: List of declared source groups to include. The entire
198#     DAG of source modules is included.
199#   COMMON_CAPI_LINK_LIBS: List of dylibs (typically one) to make every
200#     extension depend on (see mlir_python_add_common_capi_library).
201function(add_mlir_python_modules name)
202  cmake_parse_arguments(ARG
203    ""
204    "ROOT_PREFIX;INSTALL_PREFIX;COMMON_CAPI_LINK_LIBS"
205    "DECLARED_SOURCES"
206    ${ARGN})
207  # Helper to process an individual target.
208  function(_process_target modules_target sources_target)
209    get_target_property(_source_type ${sources_target} mlir_python_SOURCES_TYPE)
210
211    get_target_property(_python_root_dir ${sources_target} mlir_python_ROOT_DIR)
212    if(NOT _python_root_dir)
213      message(FATAL_ERROR "Target ${sources_target} lacks mlir_python_ROOT_DIR property")
214    endif()
215
216    if(_source_type STREQUAL "pure")
217      # Pure python sources to link into the tree.
218      get_target_property(_python_sources ${sources_target} mlir_python_SOURCES)
219      get_target_property(_specified_dest_prefix ${sources_target} mlir_python_DEST_PREFIX)
220      foreach(_source_relative_path ${_python_sources})
221        set(_dest_relative_path "${_source_relative_path}")
222        if(_specified_dest_prefix)
223          set(_dest_relative_path "${_specified_dest_prefix}/${_dest_relative_path}")
224        endif()
225        set(_src_path "${_python_root_dir}/${_source_relative_path}")
226        set(_dest_path "${ARG_ROOT_PREFIX}/${_dest_relative_path}")
227
228        get_filename_component(_dest_dir "${_dest_path}" DIRECTORY)
229        get_filename_component(_install_path "${ARG_INSTALL_PREFIX}/${_dest_relative_path}" DIRECTORY)
230
231        file(MAKE_DIRECTORY "${_dest_dir}")
232
233        # On Windows create_symlink requires special permissions. Use copy_if_different instead.
234        if(CMAKE_HOST_WIN32)
235          set(_link_or_copy copy_if_different)
236        else()
237          set(_link_or_copy create_symlink)
238        endif()
239
240        add_custom_command(
241          TARGET ${modules_target} PRE_BUILD
242          COMMENT "Copying python source ${_src_path} -> ${_dest_path}"
243          DEPENDS "${_src_path}"
244          BYPRODUCTS "${_dest_path}"
245          COMMAND "${CMAKE_COMMAND}" -E ${_link_or_copy}
246              "${_src_path}" "${_dest_path}"
247        )
248        install(
249          FILES "${_src_path}"
250          DESTINATION "${_install_path}"
251          COMPONENT ${modules_target}
252        )
253      endforeach()
254    elseif(_source_type STREQUAL "extension")
255      # Native CPP extension.
256      get_target_property(_module_name ${sources_target} mlir_python_EXTENSION_MODULE_NAME)
257      get_target_property(_cpp_sources ${sources_target} mlir_python_CPP_SOURCES)
258      get_target_property(_private_link_libs ${sources_target} mlir_python_PRIVATE_LINK_LIBS)
259      # Transform relative source to based on root dir.
260      list(TRANSFORM _cpp_sources PREPEND "${_python_root_dir}/")
261      set(_extension_target "${name}.extension.${_module_name}.dso")
262      add_mlir_python_extension(${_extension_target} "${_module_name}"
263        INSTALL_COMPONENT ${modules_target}
264        INSTALL_DIR "${ARG_INSTALL_PREFIX}/_mlir_libs"
265        OUTPUT_DIRECTORY "${ARG_ROOT_PREFIX}/_mlir_libs"
266        SOURCES ${_cpp_sources}
267        LINK_LIBS PRIVATE
268          ${_private_link_libs}
269          ${ARG_COMMON_CAPI_LINK_LIBS}
270      )
271      add_dependencies(${name} ${_extension_target})
272      mlir_python_setup_extension_rpath(${_extension_target})
273    else()
274      message(SEND_ERROR "Unrecognized source type '${_source_type}' for python source target ${sources_target}")
275      return()
276    endif()
277  endfunction()
278
279  _flatten_mlir_python_targets(_flat_targets ${ARG_DECLARED_SOURCES})
280  # Collect dependencies.
281  set(_depends)
282  foreach(sources_target ${_flat_targets})
283    get_target_property(_local_depends ${sources_target} mlir_python_FILE_DEPENDS)
284    if(_local_depends)
285      list(APPEND _depends ${_local_depends})
286    endif()
287  endforeach()
288
289  # Build the modules target.
290  add_custom_target(${name} ALL DEPENDS ${_depends})
291  foreach(sources_target ${_flat_targets})
292    _process_target(${name} ${sources_target})
293  endforeach()
294
295  # Create an install target.
296  if(NOT LLVM_ENABLE_IDE)
297    add_llvm_install_targets(
298      install-${name}
299      DEPENDS ${name}
300      COMPONENT ${name})
301  endif()
302endfunction()
303
304# Function: declare_mlir_dialect_python_bindings
305# Helper to generate source groups for dialects, including both static source
306# files and a TD_FILE to generate wrappers.
307#
308# This will generate a source group named ${ADD_TO_PARENT}.${DIALECT_NAME}.
309#
310# Arguments:
311#   ROOT_DIR: Same as for declare_mlir_python_sources().
312#   ADD_TO_PARENT: Same as for declare_mlir_python_sources(). Unique names
313#     for the subordinate source groups are derived from this.
314#   TD_FILE: Tablegen file to generate source for (relative to ROOT_DIR).
315#   DIALECT_NAME: Python name of the dialect.
316#   SOURCES: Same as declare_mlir_python_sources().
317#   SOURCES_GLOB: Same as declare_mlir_python_sources().
318#   DEPENDS: Additional dependency targets.
319function(declare_mlir_dialect_python_bindings)
320  cmake_parse_arguments(ARG
321    ""
322    "ROOT_DIR;ADD_TO_PARENT;TD_FILE;DIALECT_NAME"
323    "SOURCES;SOURCES_GLOB;DEPENDS"
324    ${ARGN})
325  # Sources.
326  set(_dialect_target "${ARG_ADD_TO_PARENT}.${ARG_DIALECT_NAME}")
327  declare_mlir_python_sources(${_dialect_target}
328    ROOT_DIR "${ARG_ROOT_DIR}"
329    ADD_TO_PARENT "${ARG_ADD_TO_PARENT}"
330    SOURCES "${ARG_SOURCES}"
331    SOURCES_GLOB "${ARG_SOURCES_GLOB}"
332  )
333
334  # Tablegen
335  if(ARG_TD_FILE)
336    set(tblgen_target "${ARG_ADD_TO}.${ARG_DIALECT_NAME}.tablegen")
337    set(td_file "${ARG_ROOT_DIR}/${ARG_TD_FILE}")
338    get_filename_component(relative_td_directory "${ARG_TD_FILE}" DIRECTORY)
339    file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${relative_td_directory}")
340    set(dialect_filename "${relative_td_directory}/_${ARG_DIALECT_NAME}_ops_gen.py")
341    set(LLVM_TARGET_DEFINITIONS ${td_file})
342    mlir_tablegen("${dialect_filename}" -gen-python-op-bindings
343                  -bind-dialect=${ARG_DIALECT_NAME})
344    add_public_tablegen_target(${tblgen_target})
345    if(ARG_DEPENDS)
346      add_dependencies(${tblgen_target} ${ARG_DEPENDS})
347    endif()
348
349    # Generated.
350    declare_mlir_python_sources("${ARG_ADD_TO_PARENT}.${ARG_DIALECT_NAME}.ops_gen"
351      ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}"
352      ADD_TO_PARENT "${_dialect_target}"
353      SOURCES "${dialect_filename}"
354    )
355  endif()
356endfunction()
357
358# Function: declare_mlir_dialect_extension_python_bindings
359# Helper to generate source groups for dialect extensions, including both
360# static source files and a TD_FILE to generate wrappers.
361#
362# This will generate a source group named ${ADD_TO_PARENT}.${EXTENSION_NAME}.
363#
364# Arguments:
365#   ROOT_DIR: Same as for declare_mlir_python_sources().
366#   ADD_TO_PARENT: Same as for declare_mlir_python_sources(). Unique names
367#     for the subordinate source groups are derived from this.
368#   TD_FILE: Tablegen file to generate source for (relative to ROOT_DIR).
369#   DIALECT_NAME: Python name of the dialect.
370#   EXTENSION_NAME: Python name of the dialect extension.
371#   SOURCES: Same as declare_mlir_python_sources().
372#   SOURCES_GLOB: Same as declare_mlir_python_sources().
373#   DEPENDS: Additional dependency targets.
374function(declare_mlir_dialect_extension_python_bindings)
375  cmake_parse_arguments(ARG
376    ""
377    "ROOT_DIR;ADD_TO_PARENT;TD_FILE;DIALECT_NAME;EXTENSION_NAME"
378    "SOURCES;SOURCES_GLOB;DEPENDS"
379    ${ARGN})
380  # Source files.
381  set(_extension_target "${ARG_ADD_TO_PARENT}.${ARG_EXTENSION_NAME}")
382  declare_mlir_python_sources(${_extension_target}
383    ROOT_DIR "${ARG_ROOT_DIR}"
384    ADD_TO_PARENT "${ARG_ADD_TO_PARENT}"
385    SOURCES "${ARG_SOURCES}"
386    SOURCES_GLOB "${ARG_SOURCES_GLOB}"
387  )
388
389  # Tablegen
390  if(ARG_TD_FILE)
391    set(tblgen_target "${ARG_ADD_TO_PARENT}.${ARG_EXTENSION_NAME}.tablegen")
392    set(td_file "${ARG_ROOT_DIR}/${ARG_TD_FILE}")
393    get_filename_component(relative_td_directory "${ARG_TD_FILE}" DIRECTORY)
394    file(MAKE_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/${relative_td_directory}")
395    set(output_filename "${relative_td_directory}/_${ARG_EXTENSION_NAME}_ops_gen.py")
396    set(LLVM_TARGET_DEFINITIONS ${td_file})
397    mlir_tablegen("${output_filename}" -gen-python-op-bindings
398                  -bind-dialect=${ARG_DIALECT_NAME}
399                  -dialect-extension=${ARG_EXTENSION_NAME})
400    add_public_tablegen_target(${tblgen_target})
401    if(ARG_DEPENDS)
402      add_dependencies(${tblgen_target} ${ARG_DEPENDS})
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 "${output_filename}"
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_SOURCES: Source groups from which to discover dependent
475#     EMBED_CAPI_LINK_LIBS.
476#   EMBED_LIBS: Additional libraries to embed (must be built with OBJECTS and
477#     have an "obj.${name}" object library associated).
478function(add_mlir_python_common_capi_library name)
479  cmake_parse_arguments(ARG
480    ""
481    "INSTALL_COMPONENT;INSTALL_DESTINATION;OUTPUT_DIRECTORY;RELATIVE_INSTALL_ROOT"
482    "DECLARED_SOURCES;EMBED_LIBS"
483    ${ARGN})
484  # Collect all explicit and transitive embed libs.
485  set(_embed_libs ${ARG_EMBED_LIBS})
486  _flatten_mlir_python_targets(_all_source_targets ${ARG_DECLARED_SOURCES})
487  foreach(t ${_all_source_targets})
488    get_target_property(_local_embed_libs ${t} mlir_python_EMBED_CAPI_LINK_LIBS)
489    if(_local_embed_libs)
490      list(APPEND _embed_libs ${_local_embed_libs})
491    endif()
492  endforeach()
493  list(REMOVE_DUPLICATES _embed_libs)
494
495  # Generate the aggregate .so that everything depends on.
496  add_mlir_aggregate(${name}
497    SHARED
498    DISABLE_INSTALL
499    EMBED_LIBS ${_embed_libs}
500  )
501
502  if(MSVC)
503    set_property(TARGET ${name} PROPERTY WINDOWS_EXPORT_ALL_SYMBOLS ON)
504  endif()
505  set_target_properties(${name} PROPERTIES
506    LIBRARY_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}"
507    BINARY_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}"
508    # Needed for windows (and don't hurt others).
509    RUNTIME_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}"
510    ARCHIVE_OUTPUT_DIRECTORY "${ARG_OUTPUT_DIRECTORY}"
511  )
512  mlir_python_setup_extension_rpath(${name}
513    RELATIVE_INSTALL_ROOT "${ARG_RELATIVE_INSTALL_ROOT}"
514  )
515  install(TARGETS ${name}
516    COMPONENT ${ARG_INSTALL_COMPONENT}
517    LIBRARY DESTINATION "${ARG_INSTALL_DESTINATION}"
518    RUNTIME DESTINATION "${ARG_INSTALL_DESTINATION}"
519  )
520
521endfunction()
522
523function(_flatten_mlir_python_targets output_var)
524  set(_flattened)
525  foreach(t ${ARGN})
526    get_target_property(_source_type ${t} mlir_python_SOURCES_TYPE)
527    get_target_property(_depends ${t} mlir_python_DEPENDS)
528    if(_source_type)
529      list(APPEND _flattened "${t}")
530      if(_depends)
531        _flatten_mlir_python_targets(_local_flattened ${_depends})
532        list(APPEND _flattened ${_local_flattened})
533      endif()
534    endif()
535  endforeach()
536  list(REMOVE_DUPLICATES _flattened)
537  set(${output_var} "${_flattened}" PARENT_SCOPE)
538endfunction()
539
540################################################################################
541# Build python extension
542################################################################################
543function(add_mlir_python_extension libname extname)
544  cmake_parse_arguments(ARG
545  ""
546  "INSTALL_COMPONENT;INSTALL_DIR;OUTPUT_DIRECTORY"
547  "SOURCES;LINK_LIBS"
548  ${ARGN})
549  if(ARG_UNPARSED_ARGUMENTS)
550    message(FATAL_ERROR " Unhandled arguments to add_mlir_python_extension(${libname}, ... : ${ARG_UNPARSED_ARGUMENTS}")
551  endif()
552  if("${ARG_SOURCES}" STREQUAL "")
553    message(FATAL_ERROR " Missing SOURCES argument to add_mlir_python_extension(${libname}, ...")
554  endif()
555
556  # The actual extension library produces a shared-object or DLL and has
557  # sources that must be compiled in accordance with pybind11 needs (RTTI and
558  # exceptions).
559  pybind11_add_module(${libname}
560    ${ARG_SOURCES}
561  )
562
563  # The extension itself must be compiled with RTTI and exceptions enabled.
564  # Also, some warning classes triggered by pybind11 are disabled.
565  target_compile_options(${libname} PRIVATE
566    $<$<OR:$<CXX_COMPILER_ID:Clang>,$<CXX_COMPILER_ID:AppleClang>,$<CXX_COMPILER_ID:GNU>>:
567      # Enable RTTI and exceptions.
568      -frtti -fexceptions
569    >
570    $<$<CXX_COMPILER_ID:MSVC>:
571      # Enable RTTI and exceptions.
572      /EHsc /GR>
573  )
574
575  # Configure the output to match python expectations.
576  set_target_properties(
577    ${libname} PROPERTIES
578    LIBRARY_OUTPUT_DIRECTORY ${ARG_OUTPUT_DIRECTORY}
579    OUTPUT_NAME "${extname}"
580    NO_SONAME ON
581  )
582
583  if(WIN32)
584    # Need to also set the RUNTIME_OUTPUT_DIRECTORY on Windows in order to
585    # control where the .dll gets written.
586    set_target_properties(
587      ${libname} PROPERTIES
588      RUNTIME_OUTPUT_DIRECTORY ${ARG_OUTPUT_DIRECTORY}
589      ARCHIVE_OUTPUT_DIRECTORY ${ARG_OUTPUT_DIRECTORY}
590    )
591  endif()
592
593  target_link_libraries(${libname}
594    PRIVATE
595    ${ARG_LINK_LIBS}
596  )
597
598  target_link_options(${libname}
599    PRIVATE
600      # On Linux, disable re-export of any static linked libraries that
601      # came through.
602      $<$<PLATFORM_ID:Linux>:LINKER:--exclude-libs,ALL>
603  )
604
605  if(WIN32)
606    # On Windows, pyconfig.h (and by extension python.h) hardcode the version of the
607    # python library which will be used for linkage depending on the flavor of the build.
608    # pybind11 has a workaround which depends on the definition of Py_DEBUG (if Py_DEBUG
609    # is not passed in as a compile definition, pybind11 undefs _DEBUG when including
610    # python.h, so that the release python library would be used).
611    # Since mlir uses pybind11, we can leverage their workaround by never directly
612    # pyconfig.h or python.h and instead relying on the pybind11 headers to include the
613    # necessary python headers. This results in mlir always linking against the
614    # release python library via the (undocumented) cmake property Python3_LIBRARY_RELEASE.
615    target_link_libraries(${libname} PRIVATE ${Python3_LIBRARY_RELEASE})
616  endif()
617
618  ################################################################################
619  # Install
620  ################################################################################
621  if(ARG_INSTALL_DIR)
622    install(TARGETS ${libname}
623      COMPONENT ${ARG_INSTALL_COMPONENT}
624      LIBRARY DESTINATION ${ARG_INSTALL_DIR}
625      ARCHIVE DESTINATION ${ARG_INSTALL_DIR}
626      # NOTE: Even on DLL-platforms, extensions go in the lib directory tree.
627      RUNTIME DESTINATION ${ARG_INSTALL_DIR}
628    )
629  endif()
630endfunction()
631