[Mlir-commits] [mlir] build multi python bindings for mlir (PR #177143)

llvmlistbot at llvm.org llvmlistbot at llvm.org
Sat Jan 24 22:56:05 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:

Hmm why we need to maintain nanobind's build instructions in MLIR instead of something like `FetchContent`s and `add_subdirectory`? It's quite hard to maintain IMHO.

https://github.com/llvm/llvm-project/pull/177143


More information about the Mlir-commits mailing list