[Mlir-commits] [mlir] build multi python bindings for mlir (PR #177143)
llvmlistbot at llvm.org
llvmlistbot at llvm.org
Sat Jan 24 23:00:19 PST 2026
================
@@ -8,6 +8,214 @@
# nomenclature, adds libraries.
################################################################################
+################################################################################
+# Multi-Python version support
+#
+# Set MLIR_PYTHON_VERSIONS to build Python bindings for multiple Python versions
+# in a single CMake configuration and build.
+#
+# Usage:
+# cmake ... -DMLIR_PYTHON_VERSIONS="3.10;3.11;3.12"
+#
+# How it works:
+# 1. libMLIRPythonCAPI.so is built once (Python version independent)
+# 2. Python extension modules (e.g., _mlir*.so) are built for each version
+# 3. Pure Python sources (*.py) are shared across all versions
+#
+# Requirements for each Python version:
+# - Python executable must be available (e.g., /usr/bin/python3.11)
+# - Python development headers must be installed
+# - pybind11 must be installed: python3.X -m pip install pybind11
+#
+# Default behavior (empty MLIR_PYTHON_VERSIONS):
+# Only build for the Python version specified by Python3_EXECUTABLE
+#
+################################################################################
+set(MLIR_PYTHON_VERSIONS "" CACHE STRING
+ "Semicolon-separated list of additional Python versions to build for (e.g., '3.10;3.11;3.12'). Empty means single version build using Python3_EXECUTABLE.")
+
+# Internal: Store discovered Python configs for each version
+set(_MLIR_MULTI_PYTHON_CONFIGS "" CACHE INTERNAL "List of successfully configured Python versions")
+
+# Function to find and configure a specific Python version
+function(_mlir_find_python_version version out_found)
+ set(${out_found} FALSE PARENT_SCOPE)
+
+ # Construct search paths for this Python version
+ set(_search_paths
+ "/usr/bin"
+ "/usr/local/bin"
+ "/opt/python/cp${version}*/bin"
+ )
+
+ # Add pyenv paths if HOME is set
+ if(DEFINED ENV{HOME})
+ list(APPEND _search_paths "$ENV{HOME}/.pyenv/versions/${version}*/bin")
+ endif()
+
+ # Try to find Python executable
+ find_program(_py_exe_${version}
+ NAMES "python${version}"
+ PATHS ${_search_paths}
+ NO_DEFAULT_PATH
+ )
+
+ if(NOT _py_exe_${version})
+ find_program(_py_exe_${version} NAMES "python${version}")
+ endif()
+
+ if(_py_exe_${version})
+ # Get include directory
+ execute_process(
+ COMMAND ${_py_exe_${version}} -c "import sysconfig; print(sysconfig.get_path('include'))"
+ OUTPUT_VARIABLE _py_include_${version}
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ RESULT_VARIABLE _result
+ )
+
+ if(_result EQUAL 0 AND EXISTS "${_py_include_${version}}/Python.h")
+ # Get pybind11 cmake dir (same method as MLIRDetectPythonEnv.cmake)
+ execute_process(
+ COMMAND ${_py_exe_${version}} -c "import pybind11;print(pybind11.get_cmake_dir(),end='')"
+ OUTPUT_VARIABLE _pybind11_dir_${version}
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ RESULT_VARIABLE _pybind11_result
+ )
+ if(NOT _pybind11_result EQUAL 0)
+ set(_pybind11_dir_${version} "")
+ endif()
+
+ # Get nanobind cmake dir (same method as MLIRDetectPythonEnv.cmake)
+ execute_process(
+ COMMAND ${_py_exe_${version}} -c "import nanobind;print(nanobind.cmake_dir(),end='')"
+ OUTPUT_VARIABLE _nanobind_dir_${version}
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ RESULT_VARIABLE _nanobind_result
+ )
+ if(NOT _nanobind_result EQUAL 0)
+ set(_nanobind_dir_${version} "")
+ endif()
+
+ # Get nanobind include dir
+ execute_process(
+ COMMAND ${_py_exe_${version}} -c "import nanobind;print(nanobind.include_dir(),end='')"
+ OUTPUT_VARIABLE _nanobind_include_${version}
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ )
+
+ # Get nanobind source dir (for building version-specific static library)
+ if(_nanobind_dir_${version})
+ get_filename_component(_nanobind_src_${version} "${_nanobind_dir_${version}}/.." ABSOLUTE)
+ endif()
+
+ # Get platform-specific extension suffix (e.g., .cpython-310-x86_64-linux-gnu.so)
+ execute_process(
+ COMMAND ${_py_exe_${version}} -c "import sysconfig;print(sysconfig.get_config_var('EXT_SUFFIX') or '',end='')"
+ OUTPUT_VARIABLE _ext_suffix_${version}
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ )
+
+ # Store in cache
+ set(MLIR_PYTHON_${version}_EXECUTABLE "${_py_exe_${version}}" CACHE INTERNAL "")
+ set(MLIR_PYTHON_${version}_INCLUDE "${_py_include_${version}}" CACHE INTERNAL "")
+ set(MLIR_PYTHON_${version}_PYBIND11_DIR "${_pybind11_dir_${version}}" CACHE INTERNAL "")
+ set(MLIR_PYTHON_${version}_NANOBIND_DIR "${_nanobind_dir_${version}}" CACHE INTERNAL "")
+ set(MLIR_PYTHON_${version}_NANOBIND_INCLUDE "${_nanobind_include_${version}}" CACHE INTERNAL "")
+ set(MLIR_PYTHON_${version}_NANOBIND_SRC "${_nanobind_src_${version}}" CACHE INTERNAL "")
+ set(MLIR_PYTHON_${version}_EXT_SUFFIX "${_ext_suffix_${version}}" CACHE INTERNAL "")
+
+ # Get version without dots for suffix (fallback)
+ string(REPLACE "." "" _ver_nodot ${version})
+ set(MLIR_PYTHON_${version}_ABI_TAG "cpython-${_ver_nodot}" CACHE INTERNAL "")
+
+ set(${out_found} TRUE PARENT_SCOPE)
+ message(STATUS "Found Python ${version}: ${_py_exe_${version}} (suffix: ${_ext_suffix_${version}})")
+ if(_pybind11_dir_${version})
+ message(STATUS " pybind11: ${_pybind11_dir_${version}}")
+ endif()
+ if(_nanobind_dir_${version})
+ message(STATUS " nanobind: ${_nanobind_dir_${version}}")
+ endif()
+ else()
+ message(STATUS "Python ${version} found but Python.h not available at ${_py_include_${version}}")
+ endif()
+ else()
+ message(STATUS "Python ${version} executable not found")
+ endif()
+
+ unset(_py_exe_${version} CACHE)
+endfunction()
+
+# Function to build a version-specific nanobind static library
+# This is needed because nanobind-static.a is Python version dependent
+function(_mlir_build_nanobind_static_for_version version)
+ string(REPLACE "." "" _ver_nodot ${version})
+ set(_target_name "nanobind-static-py${_ver_nodot}")
+
+ # Skip if already created
+ if(TARGET ${_target_name})
+ return()
+ endif()
+
+ set(_nb_src "${MLIR_PYTHON_${version}_NANOBIND_SRC}")
+ if(NOT _nb_src OR NOT EXISTS "${_nb_src}/src")
+ message(WARNING "Cannot build nanobind static library for Python ${version}: source not found")
+ return()
+ endif()
+
+ message(STATUS "Building nanobind static library for Python ${version}: ${_target_name}")
+
+ # Create static library with nanobind sources
+ add_library(${_target_name} STATIC
+ ${_nb_src}/src/nb_internals.cpp
+ ${_nb_src}/src/nb_func.cpp
+ ${_nb_src}/src/nb_type.cpp
+ ${_nb_src}/src/nb_enum.cpp
+ ${_nb_src}/src/nb_ndarray.cpp
+ ${_nb_src}/src/nb_static_property.cpp
+ ${_nb_src}/src/nb_ft.cpp
+ ${_nb_src}/src/common.cpp
+ ${_nb_src}/src/error.cpp
+ ${_nb_src}/src/trampoline.cpp
+ ${_nb_src}/src/implicit.cpp
+ )
+
+ target_include_directories(${_target_name} PUBLIC
+ ${MLIR_PYTHON_${version}_INCLUDE}
+ ${MLIR_PYTHON_${version}_NANOBIND_INCLUDE}
+ ${_nb_src}/ext/robin_map/include
+ )
+
+ set_target_properties(${_target_name} PROPERTIES
+ POSITION_INDEPENDENT_CODE ON
+ CXX_STANDARD 17
+ CXX_STANDARD_REQUIRED ON
+ )
----------------
PragmaTwice wrote:
I'm not sure we should use pre-built nanobind or merge the nanobind build tree via `FetchContent`.
https://github.com/llvm/llvm-project/pull/177143
More information about the Mlir-commits
mailing list