This is an automated email from the ASF dual-hosted git repository.

tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm-ffi.git


The following commit(s) were added to refs/heads/main by this push:
     new dc0dd2f  doc: Improve python packaging doc (#355)
dc0dd2f is described below

commit dc0dd2f6e8367e3bb244e0dc09c6a1a41495499d
Author: Junru Shao <[email protected]>
AuthorDate: Mon Dec 22 06:38:27 2025 -0800

    doc: Improve python packaging doc (#355)
    
    Made a few changes:
    - Migrate `examples/packaging` -> `examples/python_packaging`
    - Add a prerequisite section
    - Use `literalinclude` to include code from `examples/python_packaging`
    to the tutorial
    - Point to Python package docs in `tvm-ffi-stubgen` helper messages
---
 .github/workflows/ci_mainline_only.yml             |   8 +-
 docs/packaging/python_packaging.rst                | 104 +++++--------
 examples/packaging/src/extension.cc                | 131 -----------------
 .../{packaging => python_packaging}/CMakeLists.txt |   4 +-
 examples/{packaging => python_packaging}/README.md |  18 +--
 .../{packaging => python_packaging}/pyproject.toml |  14 +-
 .../python/my_ffi_extension/__init__.py            |   0
 .../python/my_ffi_extension/_ffi_api.py            |   6 +-
 .../{packaging => python_packaging}/run_example.py |  24 +--
 examples/python_packaging/src/extension.cc         |  91 ++++++++++++
 python/tvm_ffi/stub/cli.py                         | 163 +--------------------
 python/tvm_ffi/stub/consts.py                      |   1 +
 12 files changed, 175 insertions(+), 389 deletions(-)

diff --git a/.github/workflows/ci_mainline_only.yml 
b/.github/workflows/ci_mainline_only.yml
index 1912acb..dbf170b 100644
--- a/.github/workflows/ci_mainline_only.yml
+++ b/.github/workflows/ci_mainline_only.yml
@@ -163,25 +163,25 @@ jobs:
           cd examples\stable_c_abi
           call run_all.bat
 
-      - name: Run example/packaging [posix]
+      - name: Run example/python_packaging [posix]
         if: ${{ runner.os != 'Windows' }}
         env:
           CMAKE_BUILD_PARALLEL_LEVEL: ${{ steps.env_vars.outputs.cpu_count }}
         run: |
-          pushd examples/packaging
+          pushd examples/python_packaging
           # This directory will be auto-generated in `CMakeLists.txt` by 
setting `STUB_INIT ON`
           rm -rf python/my_ffi_extension
           uv pip install --verbose . --no-build-isolation
           python run_example.py
           popd
 
-      - name: Run example/packaging [windows]
+      - name: Run example/python_packaging [windows]
         if: ${{ runner.os == 'Windows' }}
         shell: pwsh
         env:
           CMAKE_BUILD_PARALLEL_LEVEL: ${{ steps.env_vars.outputs.cpu_count }}
         run: |
-          Set-Location examples/packaging
+          Set-Location examples/python_packaging
           Remove-Item -Recurse -Force python/my_ffi_extension
           uv pip install --verbose . --no-build-isolation
           python run_example.py
diff --git a/docs/packaging/python_packaging.rst 
b/docs/packaging/python_packaging.rst
index c768768..3166fdb 100644
--- a/docs/packaging/python_packaging.rst
+++ b/docs/packaging/python_packaging.rst
@@ -27,6 +27,23 @@ internals. We will cover three checkpoints:
 - Build Python wheel;
 - Automatic Python package generation tools.
 
+.. note::
+
+  All code used in this guide lives 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
+
+
 Export C++ to Python
 --------------------
 
@@ -53,13 +70,10 @@ C symbols are easier to call into.
     Macro :c:macro:`TVM_FFI_DLL_EXPORT_TYPED_FUNC` exports the function 
``AddTwo`` as
     a C symbol ``__tvm_ffi_add_two`` inside the shared library.
 
-    .. code-block:: cpp
-
-      static int AddTwo(int x) {
-        return x + 2;
-      }
-
-      TVM_FFI_DLL_EXPORT_TYPED_FUNC(add_two, AddTwo);
+    .. literalinclude:: ../../examples/python_packaging/src/extension.cc
+      :language: cpp
+      :start-after: [tvm_ffi_abi.begin]
+      :end-before: [tvm_ffi_abi.end]
 
   .. group-tab:: Python (User)
 
@@ -97,17 +111,10 @@ It registry handles type translation, error handling, and 
metadata.
     C++ function ``AddOne`` is registered with name 
``my_ffi_extension.add_one``
     in the global registry using :cpp:class:`tvm::ffi::reflection::GlobalDef`.
 
-    .. code-block:: cpp
-
-      static int AddOne(int x) {
-        return x + 1;
-      }
-
-      TVM_FFI_STATIC_INIT_BLOCK() {
-        namespace refl = tvm::ffi::reflection;
-        refl::GlobalDef()
-          .def("my_ffi_extension.add_one", AddOne);
-      }
+    .. literalinclude:: ../../examples/python_packaging/src/extension.cc
+      :language: cpp
+      :start-after: [global_function.begin]
+      :end-before: [global_function.end]
 
   .. group-tab:: Python (User)
 
@@ -166,33 +173,10 @@ makes it easy to expose:
     - a constructor, and
     - a method ``Sum`` that returns the sum of the two fields.
 
-    .. code-block:: cpp
-
-      class IntPairObj : public ffi::Object {
-       public:
-        int64_t a;
-        int64_t b;
-        IntPairObj(int64_t a, int64_t b) : a(a), b(b) {}
-
-        int64_t Sum() const {
-          return a + b;
-        }
-
-        TVM_FFI_DECLARE_OBJECT_INFO_FINAL(
-          /*type_key=*/"my_ffi_extension.IntPair",
-          /*class=*/IntPairObj,
-          /*parent_class=*/ffi::Object
-        );
-      };
-
-      TVM_FFI_STATIC_INIT_BLOCK() {
-        namespace refl = tvm::ffi::reflection;
-        refl::ObjectDef<IntPairObj>()
-          .def(refl::init<int64_t, int64_t>())
-          .def_rw("a", &IntPairObj::a, "the first field")
-          .def_rw("b", &IntPairObj::b, "the second field")
-          .def("sum", &IntPairObj::Sum, "IntPairObj::Sum() method");
-      }
+    .. literalinclude:: ../../examples/python_packaging/src/extension.cc
+      :language: cpp
+      :start-after: [object.begin]
+      :end-before: [object.end]
 
   .. group-tab:: Python (User)
 
@@ -238,15 +222,14 @@ 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.
 
-.. code-block:: cmake
-
-    add_library(my_ffi_extension SHARED src/extension.cc)
-    tvm_ffi_configure_target(my_ffi_extension STUB_DIR "./python")
-    install(TARGETS my_ffi_extension DESTINATION .)
-    tvm_ffi_install(my_ffi_extension DESTINATION .)
+.. 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, link 
against TVM-FFI library,
-generates stubs under the specified directory, and optionally debug symbols.
+generates stubs under ``STUB_DIR``, and can scaffold stub files when 
``STUB_INIT`` is
+enabled.
 
 Function ``tvm_ffi_install`` places necessary information, e.g. debug symbols 
in macOS, next to
 the shared library for proper packaging.
@@ -261,19 +244,10 @@ Define a :pep:`517` build backend in ``pyproject.toml``, 
with the following step
 - Specify the source directory of the package via ``wheel.packages``, and the 
installation
   destination via ``wheel.install-dir``.
 
-.. code-block:: toml
-
-   [build-system]
-   requires = ["scikit-build-core>=0.10.0", "apache-tvm-ffi"]
-   build-backend = "scikit_build_core.build"
-
-   [tool.scikit-build]
-   # The wheel is Python ABI-agnostic
-   wheel.py-api = "py3"
-   # The package contains the Python module at `python/my_ffi_extension`
-   wheel.packages = ["python/my_ffi_extension"]
-    # The install dir matches the import name
-   wheel.install-dir = "my_ffi_extension"
+.. literalinclude:: ../../examples/python_packaging/pyproject.toml
+  :language: toml
+  :start-after: [pyproject.build.begin]
+  :end-before: [pyproject.build.end]
 
 Once fully specified, scikit-build-core will invoke CMake and drive the 
extension building process.
 
diff --git a/examples/packaging/src/extension.cc 
b/examples/packaging/src/extension.cc
deleted file mode 100644
index 38525c9..0000000
--- a/examples/packaging/src/extension.cc
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * 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.
- */
-/*!
- * \file example.cc
- * \brief Example of a tvm-ffi based library that registers various functions.
- *
- * It is a simple example that demonstrates how to package a tvm-ffi library 
into a python wheel.
- * The library is written in C++ and can be compiled into a shared library.
- * The shared library can then be loaded into python and used to call the 
functions.
- */
-#include <tvm/ffi/container/tensor.h>
-#include <tvm/ffi/dtype.h>
-#include <tvm/ffi/error.h>
-#include <tvm/ffi/function.h>
-#include <tvm/ffi/reflection/registry.h>
-
-namespace my_ffi_extension {
-
-namespace ffi = tvm::ffi;
-
-/*!
- * \brief Raises a runtime error
- *
- * This is an example function to show how to raise and propagate
- * an error across the language boundary.
- *
- * \param msg The message to raise the error with
- */
-void RaiseError(ffi::String msg) { TVM_FFI_THROW(RuntimeError) << msg; }
-
-void AddOne(ffi::TensorView x, ffi::TensorView y) {
-  // implementation of a library function
-  TVM_FFI_ICHECK(x.ndim() == 1) << "x must be a 1D tensor";
-  DLDataType f32_dtype{kDLFloat, 32, 1};
-  TVM_FFI_ICHECK(x.dtype() == f32_dtype) << "x must be a float tensor";
-  TVM_FFI_ICHECK(y.ndim() == 1) << "y must be a 1D tensor";
-  TVM_FFI_ICHECK(y.dtype() == f32_dtype) << "y must be a float tensor";
-  TVM_FFI_ICHECK(x.size(0) == y.size(0)) << "x and y must have the same shape";
-  for (int i = 0; i < x.size(0); ++i) {
-    static_cast<float*>(y.data_ptr())[i] = 
static_cast<float*>(x.data_ptr())[i] + 1;
-  }
-}
-
-// expose global symbol add_one
-TVM_FFI_DLL_EXPORT_TYPED_FUNC(add_one, my_ffi_extension::AddOne);
-
-/*!
- * \brief Example of a custom object that is exposed to the FFI library
- */
-class IntPairObj : public tvm::ffi::Object {
- public:
-  int64_t a;
-  int64_t b;
-
-  IntPairObj() = default;
-  IntPairObj(int64_t a, int64_t b) : a(a), b(b) {}
-
-  int64_t GetFirst() const { return this->a; }
-
-  // Required: declare type information
-  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("my_ffi_extension.IntPair", IntPairObj, 
tvm::ffi::Object);
-};
-
-/*!
- * \brief Defines an explicit reference to IntPairObj
- *
- * A reference wrapper serves as a reference-counted ptr to the object.
- * you can use obj->field to access the fields of the object.
- */
-class IntPair : public tvm::ffi::ObjectRef {
- public:
-  // Constructor
-  explicit IntPair(int64_t a, int64_t b) { data_ = 
tvm::ffi::make_object<IntPairObj>(a, b); }
-
-  // Required: define object reference methods
-  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IntPair, tvm::ffi::ObjectRef, 
IntPairObj);
-};
-
-// The static initialization block is
-// called once when the library is loaded.
-TVM_FFI_STATIC_INIT_BLOCK() {
-  namespace refl = tvm::ffi::reflection;
-  // In this particular example, we use the reflection mechanisms to
-  // register the functions directly into the global function table.
-  //
-  // This is an alternative approach to TVM_FFI_DLL_EXPORT_TYPED_FUNC
-  // that exports the function directly as C symbol that follows tvm-ffi abi.
-  //
-  // - For functions that are expected to be static part of tvm_ffi_example 
project,
-  //   one can use reflection mechanisms to register the globa function.
-  // - For functions that are compiled and dynamically loaded at runtime, 
consider
-  //   using the normal export mechanism so they won't be exposed to the 
global function table.
-  //
-  // Make sure to have a unique name across all registered functions,
-  // always prefix with a package namespace name to avoid name collision.
-  //
-  // The function can then be found via tvm_ffi.get_global_func(name)
-  // If the function is expected to stay throughout the lifetime of the 
program/
-  //
-  // When registering via reflection mechanisms, the library do not need to be 
loaded via
-  // tvm::ffi::Module::LoadFromFile, instead, just load the dll or simply 
bundle into the
-  // final project
-  refl::GlobalDef().def("my_ffi_extension.raise_error", RaiseError);
-  // register the object into the system
-  // register field accessors and a global static function `__ffi_init__` as 
ffi::Function
-  refl::ObjectDef<IntPairObj>()
-      .def(refl::init<int64_t, int64_t>())
-      // Example static method that returns the second element of the pair
-      .def_static("static_get_second", [](IntPair pair) -> int64_t { return 
pair->b; })
-      // Example to bind an instance method
-      .def("get_first", &IntPairObj::GetFirst)
-      .def_ro("a", &IntPairObj::a)
-      .def_ro("b", &IntPairObj::b);
-}
-}  // namespace my_ffi_extension
diff --git a/examples/packaging/CMakeLists.txt 
b/examples/python_packaging/CMakeLists.txt
similarity index 92%
rename from examples/packaging/CMakeLists.txt
rename to examples/python_packaging/CMakeLists.txt
index 9b9fa80..d1f2879 100644
--- a/examples/packaging/CMakeLists.txt
+++ b/examples/python_packaging/CMakeLists.txt
@@ -23,7 +23,9 @@ find_package(
   REQUIRED
 )
 find_package(tvm_ffi CONFIG REQUIRED)
+# [example.cmake.begin]
 add_library(my_ffi_extension SHARED src/extension.cc)
 tvm_ffi_configure_target(my_ffi_extension STUB_DIR "./python" STUB_INIT ON)
 install(TARGETS my_ffi_extension DESTINATION .)
-tvm_ffi_install(my_ffi_extension)
+tvm_ffi_install(my_ffi_extension DESTINATION .)
+# [example.cmake.end]
diff --git a/examples/packaging/README.md b/examples/python_packaging/README.md
similarity index 74%
rename from examples/packaging/README.md
rename to examples/python_packaging/README.md
index 96d0939..e760710 100644
--- a/examples/packaging/README.md
+++ b/examples/python_packaging/README.md
@@ -31,7 +31,7 @@ packaging as well.
 Use `uv pip` (the same tooling used in CI) to build and install the example 
wheel:
 
 ```bash
-cd examples/packaging
+cd examples/python_packaging
 uv pip install --reinstall --verbose .
 ```
 
@@ -42,20 +42,12 @@ Note: When running the auditwheel process, make sure to skip
 
 ## Run the example
 
-After installing the `my_ffi_extension` example package, you can run the 
following example
-that invokes the `add_one` function exposed.
+After installing the `my_ffi_extension` example package, you can run the 
following example.
 
 ```bash
 python run_example.py
 ```
 
-This runs three flows: calling `add_one`, demonstrating `raise_error` with a 
propagated traceback, and constructing/using the `IntPair` object.
-
-When possible, tvm_ffi will try to preserve backtrace across language 
boundary. You will see output like
-
-```text
-File "src/extension.cc", line 45, in void 
my_ffi_extension::RaiseError(tvm::ffi::String)
-```
-
-If you are in an IDE like VSCode, you can click and jump to the C++ lines of 
error when
-the debug symbols are preserved.
+This runs four flows: calling `add_two` via the TVM-FFI ABI, calling `add_one` 
via the global
+registry, calling `raise_error` to demonstrate error propagation, and 
constructing/using the
+`IntPair` object.
diff --git a/examples/packaging/pyproject.toml 
b/examples/python_packaging/pyproject.toml
similarity index 89%
rename from examples/packaging/pyproject.toml
rename to examples/python_packaging/pyproject.toml
index 7385120..7652bb4 100644
--- a/examples/packaging/pyproject.toml
+++ b/examples/python_packaging/pyproject.toml
@@ -33,14 +33,19 @@ requires-python = ">=3.9"
 
 dependencies = ["apache-tvm-ffi"]
 
+# [pyproject.build.begin]
 [build-system]
 requires = ["scikit-build-core>=0.10.0", "apache-tvm-ffi"]
 build-backend = "scikit_build_core.build"
 
 [tool.scikit-build]
-# the wheel is abi agnostic
+# The wheel is Python ABI-agnostic
 wheel.py-api = "py3"
-minimum-version = "build-system.requires"
+# The package contains the Python module at `python/my_ffi_extension`
+wheel.packages = ["python/my_ffi_extension"]
+# The install dir matches the import name
+wheel.install-dir = "my_ffi_extension"
+# [pyproject.build.end]
 
 # Build configuration
 build-dir = "build"
@@ -52,7 +57,4 @@ cmake.build-type = "RelWithDebInfo"
 
 # Logging
 logging.level = "INFO"
-
-# Wheel configuration
-wheel.packages = ["python/my_ffi_extension"]
-wheel.install-dir = "my_ffi_extension"
+minimum-version = "build-system.requires"
diff --git a/examples/packaging/python/my_ffi_extension/__init__.py 
b/examples/python_packaging/python/my_ffi_extension/__init__.py
similarity index 100%
rename from examples/packaging/python/my_ffi_extension/__init__.py
rename to examples/python_packaging/python/my_ffi_extension/__init__.py
diff --git a/examples/packaging/python/my_ffi_extension/_ffi_api.py 
b/examples/python_packaging/python/my_ffi_extension/_ffi_api.py
similarity index 94%
rename from examples/packaging/python/my_ffi_extension/_ffi_api.py
rename to examples/python_packaging/python/my_ffi_extension/_ffi_api.py
index be25187..0ea71b8 100644
--- a/examples/packaging/python/my_ffi_extension/_ffi_api.py
+++ b/examples/python_packaging/python/my_ffi_extension/_ffi_api.py
@@ -33,6 +33,7 @@ LIB = _FFI_LOAD_LIB("my_ffi_extension", "my_ffi_extension")
 # fmt: off
 _FFI_INIT_FUNC("my_ffi_extension", __name__)
 if TYPE_CHECKING:
+    def add_one(_0: int, /) -> int: ...
     def raise_error(_0: str, /) -> None: ...
 # fmt: on
 # tvm-ffi-stubgen(end)
@@ -49,9 +50,7 @@ class IntPair(_ffi_Object):
     if TYPE_CHECKING:
         @staticmethod
         def __c_ffi_init__(_0: int, _1: int, /) -> Object: ...
-        @staticmethod
-        def static_get_second(_0: IntPair, /) -> int: ...
-        def get_first(self, /) -> int: ...
+        def sum(self, /) -> int: ...
     # fmt: on
     # tvm-ffi-stubgen(end)
 
@@ -60,6 +59,7 @@ __all__ = [
     # tvm-ffi-stubgen(begin): __all__
     "LIB",
     "IntPair",
+    "add_one",
     "raise_error",
     # tvm-ffi-stubgen(end)
 ]
diff --git a/examples/packaging/run_example.py 
b/examples/python_packaging/run_example.py
similarity index 74%
rename from examples/packaging/run_example.py
rename to examples/python_packaging/run_example.py
index 94c90b0..fd3c03c 100644
--- a/examples/packaging/run_example.py
+++ b/examples/python_packaging/run_example.py
@@ -19,21 +19,23 @@
 import traceback
 
 import my_ffi_extension
-import torch
+
+
+def run_add_two() -> None:
+    """Invoke add_two from the extension and print the result."""
+    print("=========== Example 1: add_two ===========")
+    print(my_ffi_extension.LIB.add_two(1))
 
 
 def run_add_one() -> None:
     """Invoke add_one from the extension and print the result."""
-    print("=========== Example 1: add_one ===========")
-    x = torch.tensor([1, 2, 3, 4, 5], dtype=torch.float32)
-    y = torch.empty_like(x)
-    my_ffi_extension.LIB.add_one(x, y)
-    print(y)
+    print("=========== Example 2: add_one ===========")
+    print(my_ffi_extension.add_one(3))
 
 
 def run_raise_error() -> None:
     """Invoke raise_error from the extension to demonstrate error handling."""
-    print("=========== Example 2: raise_error ===========")
+    print("=========== Example 3: raise_error ===========")
     try:
         my_ffi_extension.raise_error("This is an error")
     except RuntimeError:
@@ -42,13 +44,15 @@ def run_raise_error() -> None:
 
 def run_int_pair() -> None:
     """Invoke IntPair from the extension to demonstrate object handling."""
-    print("=========== Example 3: IntPair ===========")
+    print("=========== Example 4: IntPair ===========")
     pair = my_ffi_extension.IntPair(1, 2)
-    print(f"first={pair.get_first()}")
-    print(f"second={my_ffi_extension.IntPair.static_get_second(pair)}")
+    print(f"a={pair.a}")
+    print(f"b={pair.b}")
+    print(f"sum={pair.sum()}")
 
 
 if __name__ == "__main__":
+    run_add_two()
     run_add_one()
     run_raise_error()
     run_int_pair()
diff --git a/examples/python_packaging/src/extension.cc 
b/examples/python_packaging/src/extension.cc
new file mode 100644
index 0000000..ee66a04
--- /dev/null
+++ b/examples/python_packaging/src/extension.cc
@@ -0,0 +1,91 @@
+/*
+ * 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.
+ */
+/*!
+ * \file extension.cc
+ * \brief Example of a tvm-ffi based library that registers various functions.
+ */
+#include <tvm/ffi/function.h>
+#include <tvm/ffi/object.h>
+#include <tvm/ffi/reflection/registry.h>
+
+#include <cstdint>
+
+namespace my_ffi_extension {
+
+namespace ffi = tvm::ffi;
+
+// [tvm_ffi_abi.begin]
+static int AddTwo(int x) { return x + 2; }
+
+TVM_FFI_DLL_EXPORT_TYPED_FUNC(add_two, AddTwo);
+// [tvm_ffi_abi.end]
+
+// [global_function.begin]
+static int AddOne(int x) { return x + 1; }
+
+/*!
+ * \brief Raise a runtime error to demonstrate error propagation.
+ *
+ * \param msg The error message to raise.
+ *
+ * \code{.py}
+ * import my_ffi_extension
+ * try:
+ *   my_ffi_extension.raise_error("boom")
+ * except RuntimeError:
+ *   pass
+ * \endcode
+ */
+static void RaiseError(const ffi::String& msg) { TVM_FFI_THROW(RuntimeError) 
<< msg; }
+
+TVM_FFI_STATIC_INIT_BLOCK() {
+  namespace refl = tvm::ffi::reflection;
+  refl::GlobalDef()
+      .def("my_ffi_extension.add_one", AddOne)
+      .def("my_ffi_extension.raise_error", RaiseError);
+}
+// [global_function.end]
+
+// [object.begin]
+class IntPairObj : public ffi::Object {
+ public:
+  int64_t a;
+  int64_t b;
+
+  IntPairObj(int64_t a, int64_t b) : a(a), b(b) {}
+
+  int64_t Sum() const { return a + b; }
+
+  static constexpr bool _type_mutable = true;
+  TVM_FFI_DECLARE_OBJECT_INFO_FINAL(
+      /*type_key=*/"my_ffi_extension.IntPair",
+      /*class=*/IntPairObj,
+      /*parent_class=*/ffi::Object);
+};
+
+TVM_FFI_STATIC_INIT_BLOCK() {
+  namespace refl = tvm::ffi::reflection;
+  refl::ObjectDef<IntPairObj>()
+      .def(refl::init<int64_t, int64_t>())
+      .def_rw("a", &IntPairObj::a, "the first field")
+      .def_rw("b", &IntPairObj::b, "the second field")
+      .def("sum", &IntPairObj::Sum, "IntPairObj::Sum() method");
+}
+// [object.end]
+}  // namespace my_ffi_extension
diff --git a/python/tvm_ffi/stub/cli.py b/python/tvm_ffi/stub/cli.py
index f2d34a9..1540cbc 100644
--- a/python/tvm_ffi/stub/cli.py
+++ b/python/tvm_ffi/stub/cli.py
@@ -263,163 +263,14 @@ def _parse_args() -> Options:
     parser = argparse.ArgumentParser(
         prog="tvm-ffi-stubgen",
         description=(
-            "Generate type stubs for TVM FFI extensions.\n\n"
-            "In `--init-*` mode, it generates missing `_ffi_api.py` and 
`__init__.py` files, "
-            "based on the registered global functions and object types in the 
loaded libraries.\n\n"
-            "In normal mode, it processes the given files/directories 
in-place, generating "
-            "type stubs inside special `tvm-ffi-stubgen` blocks. Scroo down 
for more details."
+            "Generate type stubs for TVM FFI extensions. It supports two 
modes\n"
+            "- In `--init-*` mode, it generates missing `_ffi_api.py` and 
`__init__.py` files, "
+            "based on the registered global functions and object types in the 
loaded libraries.\n"
+            "- In normal mode, it processes the given files/directories 
in-place, generating "
+            "type stubs inside special `tvm-ffi-stubgen` directive blocks.\n\n"
+            f"Documentation: {C.TERM_CYAN}{C.DOC_URL}{C.TERM_RESET}."
         ),
         formatter_class=HelpFormatter,
-        epilog=(
-            "========\n"
-            "Examples\n"
-            "========\n\n"
-            "  # Single file\n"
-            "  tvm-ffi-stubgen python/tvm_ffi/_ffi_api.py\n\n"
-            "  # Recursively scan directories\n"
-            "  tvm-ffi-stubgen python/tvm_ffi 
examples/packaging/python/my_ffi_extension\n\n"
-            "  # Preload extension libraries\n"
-            "  tvm-ffi-stubgen --dlls 
build/libmy_ext.so;build/libmy_2nd_ext.so my_pkg/_ffi_api.py\n\n"
-            "  # Package-level init (my-ffi-extension)\n"
-            "  tvm-ffi-stubgen examples/packaging/python \\\n"
-            "    --dlls examples/packaging/build/libmy_ffi_extension.dylib 
\\\n"
-            "    --init-pypkg my-ffi-extension \\\n"
-            "    --init-lib my_ffi_extension \\\n"
-            '    --init-prefix "my_ffi_extension."\n\n'
-            "=====================\n"
-            "Syntax of stub blocks\n"
-            "=====================\n\n"
-            "Global functions\n"
-            "~~~~~~~~~~~~~~~~\n\n"
-            "    ```\n"
-            f"    {C.STUB_BEGIN} global/<registry-prefix>@<import-from 
(default: tvm_ffi)>\n"
-            f"    {C.STUB_END}\n"
-            "    ```\n\n"
-            "Generates TYPE_CHECKING-only stubs for functions in the global 
registry under the prefix.\n\n"
-            "Example:\n\n"
-            "    ```\n"
-            f"    {C.STUB_BEGIN} global/[email protected]\n"
-            "    # fmt: off\n"
-            '    _FFI_INIT_FUNC("ffi", __name__)\n'
-            "    if TYPE_CHECKING:\n"
-            "        def Array(*args: Any) -> Any: ...\n"
-            "        def ArrayGetItem(_0: Sequence[Any], _1: int, /) -> Any: 
...\n"
-            "        def ArraySize(_0: Sequence[Any], /) -> int: ...\n"
-            "        def Bytes(_0: bytes, /) -> bytes: ...\n"
-            "        ...\n"
-            "        def StructuralHash(_0: Any, _1: bool, _2: bool, /) -> 
int: ...\n"
-            "        def SystemLib(*args: Any) -> Any: ...\n"
-            "        def ToJSONGraph(_0: Any, _1: Any, /) -> Any: ...\n"
-            "        def ToJSONGraphString(_0: Any, _1: Any, /) -> str: ...\n"
-            "    # fmt: on\n"
-            f"    {C.STUB_END}\n"
-            "    ```\n\n"
-            "Objects\n"
-            "~~~~~~~\n\n"
-            "    ```\n"
-            f"    {C.STUB_BEGIN} object/<type_key>\n"
-            f"    {C.STUB_END}\n"
-            "    ```\n\n"
-            "Generates fields/methods for a class defined using TVM-FFI Object 
APIs.\n\n"
-            "Example:\n\n"
-            "    ```\n"
-            '    @register_object("ffi.reflection.AccessPath")\n'
-            "    class AccessPath(tvm_ffi.Object):\n"
-            f"        {C.STUB_BEGIN} object/ffi.reflection.AccessPath\n"
-            "        # fmt: off\n"
-            "        parent: Object | None\n"
-            "        step: AccessStep | None\n"
-            "        depth: int\n"
-            "        if TYPE_CHECKING:\n"
-            "            @staticmethod\n"
-            "            def _root() -> AccessPath: ...\n"
-            "            def _extend(self, _1: AccessStep, /) -> AccessPath: 
...\n"
-            "            def _attr(self, _1: str, /) -> AccessPath: ...\n"
-            "            def _array_item(self, _1: int, /) -> AccessPath: 
...\n"
-            "            def _map_item(self, _1: Any, /) -> AccessPath: ...\n"
-            "            def _attr_missing(self, _1: str, /) -> AccessPath: 
...\n"
-            "            def _array_item_missing(self, _1: int, /) -> 
AccessPath: ...\n"
-            "            def _map_item_missing(self, _1: Any, /) -> 
AccessPath: ...\n"
-            "            def _is_prefix_of(self, _1: AccessPath, /) -> bool: 
...\n"
-            "            def _to_steps(self, /) -> Sequence[AccessStep]: ...\n"
-            "            def _path_equal(self, _1: AccessPath, /) -> bool: 
...\n"
-            "        # fmt: on\n"
-            f"        {C.STUB_END}\n"
-            "    ```\n\n"
-            "Import section\n"
-            "~~~~~~~~~~~~~~\n\n"
-            "    ```\n"
-            f"    {C.STUB_BEGIN} import-section\n"
-            "    # fmt: off\n"
-            "    # isort: off\n"
-            "    from __future__ import annotations\n"
-            "    from ..registry import init_ffi_api as _FFI_INIT_FUNC\n"
-            "    from typing import TYPE_CHECKING\n"
-            "    if TYPE_CHECKING:\n"
-            "        from collections.abc import Mapping, Sequence\n"
-            "        from tvm_ffi import Device, Object, Tensor, dtype\n"
-            "        from tvm_ffi.testing import TestIntPair\n"
-            "        from typing import Any, Callable\n"
-            "    # isort: on\n"
-            "    # fmt: on\n"
-            f"    {C.STUB_END}\n"
-            "    ```\n\n"
-            "Auto-populates imports used by generated stubs.\n\n"
-            "Export\n"
-            "~~~~~~\n\n"
-            "    ```\n"
-            f"    {C.STUB_BEGIN} export/_ffi_api\n"
-            "    # fmt: off\n"
-            "    # isort: off\n"
-            "    from ._ffi_api import *  # noqa: F403\n"
-            "    from ._ffi_api import __all__ as _ffi_api__all__\n"
-            '    if "__all__" not in globals():\n'
-            "        __all__ = []\n"
-            "    __all__.extend(_ffi_api__all__)\n"
-            "    # isort: on\n"
-            "    # fmt: on\n"
-            f"    {C.STUB_END}\n"
-            "    ```\n\n"
-            "Re-exports a generated submodule's __all__ into the parent.\n\n"
-            "__all__\n"
-            "~~~~~~~\n\n"
-            "    ```\n"
-            "    __all__ = [\n"
-            f"        {C.STUB_BEGIN} __all__\n"
-            '        "LIB",\n'
-            '        "IntPair",\n'
-            '        "raise_error",\n'
-            f"        {C.STUB_END}\n"
-            "    ]\n"
-            "    ```\n\n"
-            "Populates __all__ with generated classes/functions and LIB (if 
present).\n\n"
-            "Type map\n"
-            "~~~~~~~~\n\n"
-            "    ```\n"
-            f"    {C.STUB_TY_MAP} <type_key> -> <python_type>\n"
-            "    ```\n\n"
-            "Maps runtime type keys to Python types used in generation.\n\n"
-            "Example:\n\n"
-            "    ```\n"
-            f"    {C.STUB_TY_MAP} ffi.reflection.AccessStep -> 
ffi.access_path.AccessStep\n"
-            "    ```\n\n"
-            "Import object\n"
-            "~~~~~~~~~~~~~\n\n"
-            "    ```\n"
-            f"    {C.STUB_IMPORT_OBJECT} <from>; <type_checking_only>; 
<alias>\n"
-            "    ```\n\n"
-            "Injects a custom import into generated code, optionally 
TYPE_CHECKING-only.\n\n"
-            "Example:\n\n"
-            "    ```\n"
-            f"    {C.STUB_IMPORT_OBJECT} ffi.Object;False;_ffi_Object\n"
-            "    ```\n\n"
-            "Skip file\n"
-            "~~~~~~~~~\n\n"
-            "    ```\n"
-            f"    {C.STUB_SKIP_FILE}\n"
-            "    ```\n\n"
-            "Prevents stubgen from modifying the file."
-        ),
     )
     parser.add_argument(
         "--imports",
@@ -486,7 +337,7 @@ def _parse_args() -> Options:
         metavar="PATH",
         help=(
             "Files or directories to process. Directories are scanned 
recursively; "
-            "only .py and .pyi files are modified. Use tvm-ffi-stubgen markers 
to "
+            "only .py and .pyi files are modified. Use tvm-ffi-stubgen 
directives to "
             "select where stubs are generated."
         ),
     )
diff --git a/python/tvm_ffi/stub/consts.py b/python/tvm_ffi/stub/consts.py
index b6458e1..2beb388 100644
--- a/python/tvm_ffi/stub/consts.py
+++ b/python/tvm_ffi/stub/consts.py
@@ -47,6 +47,7 @@ TERM_BLUE = "\033[34m"
 TERM_MAGENTA = "\033[35m"
 TERM_CYAN = "\033[36m"
 TERM_WHITE = "\033[37m"
+DOC_URL = 
"https://tvm.apache.org/ffi/packaging/python_packaging.html#stub-generation-tool";
 
 DEFAULT_SOURCE_EXTS = {".py", ".pyi"}
 TY_MAP_DEFAULTS = {

Reply via email to