Skip to content

Commit 56e85b6

Browse files
committed
[MLIR][Python] reland stubgen
1 parent 3c810b7 commit 56e85b6

File tree

14 files changed

+200
-3190
lines changed

14 files changed

+200
-3190
lines changed

.ci/all_requirements.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,9 @@ ml-dtypes==0.5.1 ; python_version < "3.13" \
194194
--hash=sha256:d13755f8e8445b3870114e5b6240facaa7cb0c3361e54beba3e07fa912a6e12b \
195195
--hash=sha256:fd918d4e6a4e0c110e2e05be7a7814d10dc1b95872accbf6512b80a109b71ae1
196196
# via -r mlir/python/requirements.txt
197-
nanobind==2.7.0 \
198-
--hash=sha256:73b12d0e751d140d6c1bf4b215e18818a8debfdb374f08dc3776ad208d808e74 \
199-
--hash=sha256:f9f1b160580c50dcf37b6495a0fd5ec61dc0d95dae5f8004f87dd9ad7eb46b34
197+
nanobind==2.9.2 \
198+
--hash=sha256:c37957ffd5eac7eda349cff3622ecd32e5ee1244ecc912c99b5bc8188bafd16e \
199+
--hash=sha256:e7608472de99d375759814cab3e2c94aba3f9ec80e62cfef8ced495ca5c27d6e
200200
# via -r mlir/python/requirements.txt
201201
numpy==2.0.2 \
202202
--hash=sha256:0123ffdaa88fa4ab64835dcbde75dcdf89c453c922f18dced6e27c90d1d0ec5a \
@@ -383,6 +383,10 @@ swig==4.3.1 \
383383
--hash=sha256:efec16327029f682f649a26da726bb0305be8800bd0f1fa3e81bf0769cf5b476 \
384384
--hash=sha256:fc496c0d600cf1bb2d91e28d3d6eae9c4301e5ea7a0dec5a4281b5efed4245a8
385385
# via -r lldb/test/requirements.txt
386+
typing-extensions==4.15.0 \
387+
--hash=sha256:0cea48d173cc12fa28ecabc3b837ea3cf6f38c6d1136f85cbaaf598984861466 \
388+
--hash=sha256:f0fa19c6845758ab08074a0cfa8b7aecb71c999ca73d62883bc25cc018c4e548
389+
# via -r mlir/python/requirements.txt
386390
urllib3==2.5.0 \
387391
--hash=sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760 \
388392
--hash=sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc

mlir/cmake/modules/AddMLIRPython.cmake

Lines changed: 156 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,79 @@ function(declare_mlir_python_sources name)
9999
endif()
100100
endfunction()
101101

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+
102175
# Function: declare_mlir_python_extension
103176
# Declares a buildable python extension from C++ source files. The built
104177
# module is considered a python source file and included as everything else.
@@ -115,11 +188,15 @@ endfunction()
115188
# on. These will be collected for all extensions and put into an
116189
# aggregate dylib that is linked against.
117190
# 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).
118195
function(declare_mlir_python_extension name)
119196
cmake_parse_arguments(ARG
120197
""
121198
"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"
123200
${ARGN})
124201

125202
if(NOT ARG_ROOT_DIR)
@@ -132,15 +209,33 @@ function(declare_mlir_python_extension name)
132209
endif()
133210

134211
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+
135229
set_target_properties(${name} PROPERTIES
136230
# Yes: Leading-lowercase property names are load bearing and the recommended
137231
# 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"
139233
mlir_python_SOURCES_TYPE extension
140234
mlir_python_EXTENSION_MODULE_NAME "${ARG_MODULE_NAME}"
141235
mlir_python_EMBED_CAPI_LINK_LIBS "${ARG_EMBED_CAPI_LINK_LIBS}"
142236
mlir_python_DEPENDS ""
143237
mlir_python_BINDINGS_LIBRARY "${ARG_PYTHON_BINDINGS_LIBRARY}"
238+
mlir_python_GENERATE_TYPE_STUBS "${ARG_GENERATE_TYPE_STUBS}"
144239
)
145240

146241
# Set the interface source and link_libs properties of the target
@@ -206,10 +301,12 @@ endfunction()
206301
# DAG of source modules is included.
207302
# COMMON_CAPI_LINK_LIBS: List of dylibs (typically one) to make every
208303
# 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.
209306
function(add_mlir_python_modules name)
210307
cmake_parse_arguments(ARG
211308
""
212-
"ROOT_PREFIX;INSTALL_PREFIX"
309+
"ROOT_PREFIX;INSTALL_PREFIX;PACKAGE_PREFIX"
213310
"COMMON_CAPI_LINK_LIBS;DECLARED_SOURCES"
214311
${ARGN})
215312
# Helper to process an individual target.
@@ -243,6 +340,42 @@ function(add_mlir_python_modules name)
243340
)
244341
add_dependencies(${modules_target} ${_extension_target})
245342
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()
246379
else()
247380
message(SEND_ERROR "Unrecognized source type '${_source_type}' for python source target ${sources_target}")
248381
return()
@@ -678,26 +811,28 @@ function(add_mlir_python_extension libname extname)
678811
# the super project handle compile options as it wishes.
679812
get_property(NB_LIBRARY_TARGET_NAME TARGET ${libname} PROPERTY LINK_LIBRARIES)
680813
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})
690824

691825
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})
701836
endif()
702837

703838
if(APPLE)

mlir/examples/standalone/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ if(MLIR_ENABLE_BINDINGS_PYTHON)
5555
include(MLIRDetectPythonEnv)
5656
mlir_configure_python_dev_packages()
5757
set(MLIR_PYTHON_PACKAGE_PREFIX "mlir_standalone" CACHE STRING "" FORCE)
58-
set(MLIR_BINDINGS_PYTHON_INSTALL_PREFIX "python_packages/standalone/mlir_standalone" CACHE STRING "" FORCE)
58+
set(MLIR_BINDINGS_PYTHON_INSTALL_PREFIX "python_packages/standalone/${MLIR_PYTHON_PACKAGE_PREFIX}" CACHE STRING "" FORCE)
5959
add_subdirectory(python)
6060
endif()
6161
add_subdirectory(test)

mlir/examples/standalone/python/CMakeLists.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ declare_mlir_python_extension(StandalonePythonSources.NanobindExtension
3939
EMBED_CAPI_LINK_LIBS
4040
StandaloneCAPI
4141
PYTHON_BINDINGS_LIBRARY nanobind
42+
GENERATE_TYPE_STUBS
43+
"_standaloneDialectsNanobind/__init__.pyi"
44+
"_standaloneDialectsNanobind/standalone.pyi"
4245
)
4346

4447

@@ -76,4 +79,5 @@ add_mlir_python_modules(StandalonePythonModules
7679
MLIRPythonSources.Dialects.builtin
7780
COMMON_CAPI_LINK_LIBS
7881
StandalonePythonCAPI
79-
)
82+
PACKAGE_PREFIX "${MLIR_PYTHON_PACKAGE_PREFIX}"
83+
)

0 commit comments

Comments
 (0)