@@ -99,6 +99,79 @@ function(declare_mlir_python_sources name)
99
99
endif ()
100
100
endfunction ()
101
101
102
+ # Function: generate_type_stubs
103
+ # Turns on automatic type stub generation (via nanobind's stubgen) for extension modules.
104
+ # Arguments:
105
+ # FQ_MODULE_NAME: The fully-qualified name of the extension module (used for importing in python).
106
+ # DEPENDS_TARGET: The dso target corresponding to the extension module
107
+ # (e.g., something like StandalonePythonModules.extension._standaloneDialectsNanobind.dso)
108
+ # CORE_MLIR_DEPENDS_TARGET: The dso target corresponding to the main/core extension module
109
+ # (e.g., something like StandalonePythonModules.extension._mlir.dso)
110
+ # OUTPUT_DIR: The root output directory to emit the type stubs into.
111
+ # OUTPUTS: List of expected outputs.
112
+ # DEPENDS_TARGET_SRC_DEPS: List of cpp sources for extension library (for generating a DEPFILE).
113
+ # IMPORT_PATH: The path that should be added to PYTHONPATH to successfully import the extension module.
114
+ # Most likely this is ${ARG_ROOT_PREFIX} passed to add_mlir_python_modules.
115
+ # Outputs:
116
+ # NB_STUBGEN_CUSTOM_TARGET: The target corresponding to generation which other targets can depend on.
117
+ function (generate_type_stubs )
118
+ cmake_parse_arguments (ARG
119
+ ""
120
+ "FQ_MODULE_NAME;DEPENDS_TARGET;CORE_MLIR_DEPENDS_TARGET;OUTPUT_DIR;IMPORT_PATH"
121
+ "OUTPUTS;DEPENDS_TARGET_SRC_DEPS"
122
+ ${ARGN} )
123
+ # for people doing find_package(nanobind)
124
+ if (EXISTS ${nanobind_DIR} /../src/stubgen.py )
125
+ set (NB_STUBGEN "${nanobind_DIR} /../src/stubgen.py" )
126
+ elseif (EXISTS ${nanobind_DIR} /../stubgen.py )
127
+ set (NB_STUBGEN "${nanobind_DIR} /../stubgen.py" )
128
+ # for people using FetchContent_Declare and FetchContent_MakeAvailable
129
+ elseif (EXISTS ${nanobind_SOURCE_DIR} /src/stubgen.py )
130
+ set (NB_STUBGEN "${nanobind_SOURCE_DIR} /src/stubgen.py" )
131
+ elseif (EXISTS ${nanobind_SOURCE_DIR} /stubgen.py )
132
+ set (NB_STUBGEN "${nanobind_SOURCE_DIR} /stubgen.py" )
133
+ else ()
134
+ message (FATAL_ERROR "generate_type_stubs(): could not locate 'stubgen.py'!" )
135
+ endif ()
136
+
137
+ file (REAL_PATH "${NB_STUBGEN} " NB_STUBGEN )
138
+ file (REAL_PATH "${ARG_IMPORT_PATH} " _import_path )
139
+ set (_nb_stubgen_cmd
140
+ "${Python_EXECUTABLE} "
141
+ "${NB_STUBGEN} "
142
+ --module
143
+ "${ARG_FQ_MODULE_NAME} "
144
+ -i
145
+ "${_import_path} "
146
+ --recursive
147
+ --include-private
148
+ --output-dir
149
+ "${ARG_OUTPUT_DIR} "
150
+ --quiet )
151
+
152
+ list (TRANSFORM ARG_OUTPUTS PREPEND "${ARG_OUTPUT_DIR} /" OUTPUT_VARIABLE _generated_type_stubs )
153
+ set (_depfile "${ARG_OUTPUT_DIR} /${ARG_FQ_MODULE_NAME} .d" )
154
+ if ((NOT EXISTS ${_depfile} ) AND ARG_DEPENDS_TARGET_SRC_DEPS )
155
+ list (JOIN ARG_DEPENDS_TARGET_SRC_DEPS " " _depfiles )
156
+ list (TRANSFORM _generated_type_stubs APPEND ": ${_depfiles} " OUTPUT_VARIABLE _depfiles )
157
+ list (JOIN _depfiles "\n " _depfiles )
158
+ file (GENERATE OUTPUT "${_depfile} " CONTENT "${_depfiles} " )
159
+ endif ()
160
+ add_custom_command (
161
+ OUTPUT ${_generated_type_stubs}
162
+ COMMAND ${_nb_stubgen_cmd}
163
+ WORKING_DIRECTORY "${CMAKE_CURRENT_FUNCTION_LIST_DIR} "
164
+ DEPENDS
165
+ "${ARG_CORE_MLIR_DEPENDS_TARGET} .extension._mlir.dso"
166
+ "${ARG_CORE_MLIR_DEPENDS_TARGET} .sources.MLIRPythonSources.Core.Python"
167
+ "${ARG_DEPENDS_TARGET} "
168
+ DEPFILE "${_depfile} "
169
+ )
170
+ set (_name "${ARG_CORE_MLIR_DEPENDS_TARGET} .${ARG_DEPENDS_TARGET} .${ARG_FQ_MODULE_NAME} .type_stubs" )
171
+ add_custom_target ("${_name} " DEPENDS ${_generated_type_stubs} )
172
+ set (NB_STUBGEN_CUSTOM_TARGET "${_name} " PARENT_SCOPE )
173
+ endfunction ()
174
+
102
175
# Function: declare_mlir_python_extension
103
176
# Declares a buildable python extension from C++ source files. The built
104
177
# module is considered a python source file and included as everything else.
@@ -115,11 +188,15 @@ endfunction()
115
188
# on. These will be collected for all extensions and put into an
116
189
# aggregate dylib that is linked against.
117
190
# PYTHON_BINDINGS_LIBRARY: Either pybind11 or nanobind.
191
+ # GENERATE_TYPE_STUBS: Either
192
+ # 1. OFF (default)
193
+ # 2. ON if ${MODULE_NAME}.pyi is the only stub
194
+ # 3. A list of generated type stubs expected from stubgen (relative to _mlir_libs).
118
195
function (declare_mlir_python_extension name )
119
196
cmake_parse_arguments (ARG
120
197
""
121
198
"ROOT_DIR;MODULE_NAME;ADD_TO_PARENT;PYTHON_BINDINGS_LIBRARY"
122
- "SOURCES;PRIVATE_LINK_LIBS;EMBED_CAPI_LINK_LIBS"
199
+ "SOURCES;PRIVATE_LINK_LIBS;EMBED_CAPI_LINK_LIBS;GENERATE_TYPE_STUBS "
123
200
${ARGN} )
124
201
125
202
if (NOT ARG_ROOT_DIR )
@@ -132,15 +209,33 @@ function(declare_mlir_python_extension name)
132
209
endif ()
133
210
134
211
add_library (${name} INTERFACE )
212
+
213
+ if (NOT ARG_GENERATE_TYPE_STUBS )
214
+ set (ARG_GENERATE_TYPE_STUBS OFF )
215
+ endif ()
216
+ if ("${ARG_GENERATE_TYPE_STUBS} " STREQUAL "ON" )
217
+ set (ARG_GENERATE_TYPE_STUBS "${ARG_MODULE_NAME} .pyi" )
218
+ endif ()
219
+ if (ARG_GENERATE_TYPE_STUBS )
220
+ list (TRANSFORM ARG_GENERATE_TYPE_STUBS PREPEND "_mlir_libs/" )
221
+ declare_mlir_python_sources (
222
+ "${name} .type_stub_gen"
223
+ ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR} /type_stubs"
224
+ ADD_TO_PARENT "${ARG_ADD_TO_PARENT} "
225
+ SOURCES "${ARG_GENERATE_TYPE_STUBS} "
226
+ )
227
+ endif ()
228
+
135
229
set_target_properties (${name} PROPERTIES
136
230
# Yes: Leading-lowercase property names are load bearing and the recommended
137
231
# 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"
232
+ EXPORT_PROPERTIES "mlir_python_SOURCES_TYPE;mlir_python_EXTENSION_MODULE_NAME;mlir_python_EMBED_CAPI_LINK_LIBS;mlir_python_DEPENDS;mlir_python_BINDINGS_LIBRARY;mlir_python_GENERATE_TYPE_STUBS "
139
233
mlir_python_SOURCES_TYPE extension
140
234
mlir_python_EXTENSION_MODULE_NAME "${ARG_MODULE_NAME} "
141
235
mlir_python_EMBED_CAPI_LINK_LIBS "${ARG_EMBED_CAPI_LINK_LIBS} "
142
236
mlir_python_DEPENDS ""
143
237
mlir_python_BINDINGS_LIBRARY "${ARG_PYTHON_BINDINGS_LIBRARY} "
238
+ mlir_python_GENERATE_TYPE_STUBS "${ARG_GENERATE_TYPE_STUBS} "
144
239
)
145
240
146
241
# Set the interface source and link_libs properties of the target
@@ -206,10 +301,12 @@ endfunction()
206
301
# DAG of source modules is included.
207
302
# COMMON_CAPI_LINK_LIBS: List of dylibs (typically one) to make every
208
303
# extension depend on (see mlir_python_add_common_capi_library).
304
+ # PACKAGE_PREFIX: Same as MLIR_PYTHON_PACKAGE_PREFIX not including trailing `.`.
305
+ # This is used to determine type stub generation python module names.
209
306
function (add_mlir_python_modules name )
210
307
cmake_parse_arguments (ARG
211
308
""
212
- "ROOT_PREFIX;INSTALL_PREFIX"
309
+ "ROOT_PREFIX;INSTALL_PREFIX;PACKAGE_PREFIX "
213
310
"COMMON_CAPI_LINK_LIBS;DECLARED_SOURCES"
214
311
${ARGN} )
215
312
# Helper to process an individual target.
@@ -243,6 +340,42 @@ function(add_mlir_python_modules name)
243
340
)
244
341
add_dependencies (${modules_target} ${_extension_target} )
245
342
mlir_python_setup_extension_rpath (${_extension_target} )
343
+ # NOTE: `sources_target` (naturally) lists all the sources (it's the INTERFACE
344
+ # target defined above in declare_mlir_python_extension). It's also the name of the
345
+ # target that gets exported (i.e., is populated as an INTERFACE IMPORTED library in MLIRTargets.cmake).
346
+ # This is why all metadata is queried from `sources_target`. On the other hand
347
+ # `_extension_target` is the actual dylib target that's built just above with `add_mlir_python_extension`.
348
+ # That's why dependencies are in terms of `_extension_target`.
349
+ get_target_property (_generate_type_stubs ${sources_target} mlir_python_GENERATE_TYPE_STUBS )
350
+ if (_generate_type_stubs )
351
+ if ((NOT ARG_PACKAGE_PREFIX ) OR ("${ARG_PACKAGE_PREFIX} " STREQUAL "" ))
352
+ message (FATAL_ERROR "GENERATE_TYPE_STUBS requires PACKAGE_PREFIX for ${name} " )
353
+ endif ()
354
+ # TL;DR: all paths here are load bearing and annoyingly coupled. Changing paths here
355
+ # (or related code in declare_mlir_python_extension) will break either in-tree or out-of-tree generation.
356
+ #
357
+ # We remove _mlir_libs here because OUTPUT_DIR already includes it.
358
+ # Specifically OUTPUT_DIR already includes it because that's the actual directory
359
+ # where we want stubgen to dump the emitted sources. This is load bearing because up above
360
+ # (in declare_mlir_python_extension) we prefixed all the paths with _mlir_libs and
361
+ # we specified declare_mlir_python_sources with ROOT_DIR "${CMAKE_CURRENT_BINARY_DIR}/type_stubs".
362
+ #
363
+ # NOTE: INTERFACE_SOURCES is a genex in the build dir ($<BUILD_INTERFACE> $<INSTALL_INTERFACE>)
364
+ # which will be evaluated by file(GENERATE ...). In the install dir it's a conventional path
365
+ # (see install/lib/cmake/mlir/MLIRTargets.cmake).
366
+ get_target_property (_extension_srcs ${sources_target} INTERFACE_SOURCES )
367
+ list (TRANSFORM _generate_type_stubs REPLACE "_mlir_libs/" "" )
368
+ generate_type_stubs (
369
+ FQ_MODULE_NAME "${ARG_PACKAGE_PREFIX} ._mlir_libs.${_module_name} "
370
+ DEPENDS_TARGET ${_extension_target}
371
+ CORE_MLIR_DEPENDS_TARGET ${name}
372
+ OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR} /type_stubs/_mlir_libs"
373
+ OUTPUTS "${_generate_type_stubs} "
374
+ DEPENDS_TARGET_SRC_DEPS "${_extension_srcs} "
375
+ IMPORT_PATH "${ARG_ROOT_PREFIX} /.."
376
+ )
377
+ add_dependencies ("${modules_target} " "${NB_STUBGEN_CUSTOM_TARGET} " )
378
+ endif ()
246
379
else ()
247
380
message (SEND_ERROR "Unrecognized source type '${_source_type} ' for python source target ${sources_target} " )
248
381
return ()
@@ -678,26 +811,28 @@ function(add_mlir_python_extension libname extname)
678
811
# the super project handle compile options as it wishes.
679
812
get_property (NB_LIBRARY_TARGET_NAME TARGET ${libname} PROPERTY LINK_LIBRARIES )
680
813
target_compile_options (${NB_LIBRARY_TARGET_NAME}
681
- PRIVATE
682
- -Wall -Wextra -Wpedantic
683
- -Wno-c++98-compat-extra-semi
684
- -Wno-cast-qual
685
- -Wno-covered-switch-default
686
- -Wno-nested-anon-types
687
- -Wno-unused-parameter
688
- -Wno-zero-length-array
689
- ${eh_rtti_enable} )
814
+ PRIVATE
815
+ -Wall -Wextra -Wpedantic
816
+ -Wno-c++98-compat-extra-semi
817
+ -Wno-cast-qual
818
+ -Wno-covered-switch-default
819
+ -Wno-deprecated-literal-operator
820
+ -Wno-nested-anon-types
821
+ -Wno-unused-parameter
822
+ -Wno-zero-length-array
823
+ ${eh_rtti_enable} )
690
824
691
825
target_compile_options (${libname}
692
- PRIVATE
693
- -Wall -Wextra -Wpedantic
694
- -Wno-c++98-compat-extra-semi
695
- -Wno-cast-qual
696
- -Wno-covered-switch-default
697
- -Wno-nested-anon-types
698
- -Wno-unused-parameter
699
- -Wno-zero-length-array
700
- ${eh_rtti_enable} )
826
+ PRIVATE
827
+ -Wall -Wextra -Wpedantic
828
+ -Wno-c++98-compat-extra-semi
829
+ -Wno-cast-qual
830
+ -Wno-covered-switch-default
831
+ -Wno-deprecated-literal-operator
832
+ -Wno-nested-anon-types
833
+ -Wno-unused-parameter
834
+ -Wno-zero-length-array
835
+ ${eh_rtti_enable} )
701
836
endif ()
702
837
703
838
if (APPLE )
0 commit comments