..  Licensed to the Apache Software Foundation (ASF) under one
    or more contributor license agreements.  See the NOTICE file
    distributed with this work for additional information
    regarding copyright ownership.  The ASF licenses this file
    to you under the Apache License, Version 2.0 (the
    "License"); you may not use this file except in compliance
    with the License.  You may obtain a copy of the License at

..    http://www.apache.org/licenses/LICENSE-2.0

..  Unless required by applicable law or agreed to in writing,
    software distributed under the License is distributed on an
    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    KIND, either express or implied.  See the License for the
    specific language governing permissions and limitations
    under the License.

Python Packaging
================

This guide walks through a small, complete workflow for packaging a TVM-FFI extension
as a Python wheel. The goal is to help you wire up a simple extension, produce a wheel,
and ship user-friendly typing annotations without needing to know every detail of TVM
internals. We cover three checkpoints:

- Build a Python wheel;
- Export C++ to Python;
- Generate Python package stubs.

.. note::

  All code used in this guide is under
  `examples/python_packaging <https://github.com/apache/tvm-ffi/tree/main/examples/python_packaging>`_.

.. admonition:: Prerequisite
   :class: hint

   - Python: 3.9 or newer (for the ``tvm_ffi.config``/``tvm-ffi-config`` helpers)
   - Compiler: C11-capable toolchain (GCC/Clang/MSVC)
   - TVM-FFI installed via

     .. code-block:: bash

        pip install --reinstall --upgrade apache-tvm-ffi


Build Python Wheel
------------------

Start by defining the Python packaging and build wiring. TVM-FFI provides helpers to build and ship
ABI-agnostic Python extensions using standard packaging tools. The steps below set up the build so
you can plug in the C++ exports from the next section.

The flow below uses :external+scikit_build_core:doc:`scikit-build-core <index>`
to drive a CMake build, but the same ideas apply to setuptools or other :pep:`517` backends.

CMake Target
~~~~~~~~~~~~

Assume the source tree contains ``src/extension.cc``. Create a ``CMakeLists.txt`` that
creates a shared target ``my_ffi_extension`` and configures it against TVM-FFI.

.. literalinclude:: ../../examples/python_packaging/CMakeLists.txt
  :language: cmake
  :start-after: [example.cmake.begin]
  :end-before: [example.cmake.end]

Function ``tvm_ffi_configure_target`` sets up TVM-FFI include paths and links against the TVM-FFI library.
Additional options for stub generation are covered in :ref:`sec-stubgen`.

Function ``tvm_ffi_install`` places necessary information (e.g., debug symbols on macOS) next to
the shared library for packaging.

Python Build Backend
~~~~~~~~~~~~~~~~~~~~

Define a :pep:`517` build backend in ``pyproject.toml`` with the following steps:

- Specify ``apache-tvm-ffi`` as a build requirement, so that CMake can find TVM-FFI;
- Configure ``wheel.py-api`` that indicates a Python ABI-agnostic wheel;
- Specify the source directory of the package via ``wheel.packages``, and the installation
  destination via ``wheel.install-dir``.

.. literalinclude:: ../../examples/python_packaging/pyproject.toml
  :language: toml
  :start-after: [pyproject.build.begin]
  :end-before: [pyproject.build.end]

Once specified, scikit-build-core will invoke CMake and drive the extension build.


Wheel Auditing
~~~~~~~~~~~~~~

**Build wheels**. You can build wheels using standard workflows, for example:

- `pip workflow <https://pip.pypa.io/en/stable/cli/pip_wheel/>`_ or `editable install <https://pip.pypa.io/en/stable/topics/local-project-installs/#editable-installs>`_

.. code-block:: bash

  # editable install
  pip install -e .
  # standard wheel build
  pip wheel -w dist .

- `uv workflow <https://docs.astral.sh/uv/guides/package/>`_

.. code-block:: bash

  uv build --wheel --out-dir dist .

- `cibuildwheel <https://cibuildwheel.pypa.io/>`_ for multi-platform build

.. code-block:: bash

  cibuildwheel --output-dir dist

**Audit wheels**. In practice, an extra step is usually needed to remove redundant
and error-prone shared library dependencies. In our case, because ``libtvm_ffi.so``
(or its platform variants) is guaranteed to be loaded by importing ``tvm_ffi``,
we can safely exclude this dependency from the final wheel.

.. code-block:: bash

   # Linux
   auditwheel repair --exclude libtvm_ffi.so dist/*.whl
   # macOS
   delocate-wheel -w dist -v --exclude libtvm_ffi.dylib dist/*.whl
   # Windows
   delvewheel repair --exclude tvm_ffi.dll -w dist dist\\*.whl

.. _sec-load-the-library:

Load the Library
~~~~~~~~~~~~~~~~

Once the wheel is installed, use :py:func:`tvm_ffi.libinfo.load_lib_module` to load
the shared library:

.. code-block:: python

   from tvm_ffi.libinfo import load_lib_module

   LIB = load_lib_module(
       package="my-ffi-extension",
       target_name="my_ffi_extension",
   )

The parameters are:

- ``package``: The Python package name as registered with pip (e.g., ``"my-ffi-extension"``
  or ``"apache-tvm-ffi"``). This is the name in ``pyproject.toml``, **not** the import name
  (e.g., ``tvm_ffi``). The function uses ``importlib.metadata.distribution(package)`` internally
  to locate installed package files.

- ``target_name``: The CMake target name (e.g., ``"my_ffi_extension"``). It is used to derive
  the platform-specific shared library filename:

  * Linux: ``lib{target_name}.so``
  * macOS: ``lib{target_name}.dylib``
  * Windows: ``{target_name}.dll``

Once the library is loaded, functions and classes can be exported from C++ and
called from Python. See :doc:`../guides/export_func_cls` for the three export
mechanisms (C symbols, global functions, and classes) with complete examples.


Stub Generation
---------------

TVM-FFI provides a stub generation tool ``tvm-ffi-stubgen`` that creates Python type hints
from C++ reflection metadata. The tool integrates with CMake and can generate complete
stub files automatically, or update existing files using special directive comments.

For most projects, enable automatic stub generation in CMake:

.. code-block:: cmake

   tvm_ffi_configure_target(my_ffi_extension
       STUB_DIR "python"
       STUB_INIT ON
   )

This generates ``_ffi_api.py`` and ``__init__.py`` files with proper type hints for all
registered global functions and classes.

.. seealso::

   - :doc:`stubgen`: Complete stub generation guide, including directive-based
     customization and command-line usage.
   - :doc:`../dev/ci_cd`: Reproducing wheel builds locally with cibuildwheel.
