File: bazel.rst

package info (click to toggle)
nanobind 2.9.2-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 3,060 kB
  • sloc: cpp: 11,838; python: 5,862; ansic: 4,820; makefile: 22; sh: 15
file content (198 lines) | stat: -rw-r--r-- 7,581 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
.. _bazel:

Building extensions using Bazel
===============================

If you prefer the Bazel build system to CMake, you can build extensions using
the `nanobind-bazel <https://github.com/nicholasjng/nanobind-bazel>`__ project.

.. note::

    This project is a community contribution maintained by
    `Nicholas Junge <https://github.com/nicholasjng>`__, please report issues
    directly in the nanobind-bazel repository linked above.

.. _bazel-setup:

Adding nanobind-bazel to your Bazel project
-------------------------------------------

To use nanobind-bazel in your project, you need to add it to your project's
dependency graph. Using bzlmod, the de-facto dependency management system
in Bazel starting with version 7.0, you can simply specify it as a ``bazel_dep``
in your MODULE.bazel file:

.. code-block:: python

    # Place this in your MODULE.bazel file.
    # The major version of nanobind-bazel is equal to the version
    # of the internally used nanobind.
    # In this case, we are building bindings with nanobind v2.8.0.
    bazel_dep(name = "nanobind_bazel", version = "2.8.0")

To instead use a development version from GitHub, you can declare the
dependency as a ``git_override()`` in your MODULE.bazel:

.. code-block:: python

    # MODULE.bazel
    bazel_dep(name = "nanobind_bazel", version = "")
    git_override(
        module_name = "nanobind_bazel",
        commit = COMMIT_SHA, # replace this with the actual commit you want.
        remote = "https://github.com/nicholasjng/nanobind-bazel",
    )

In local development scenarios, you can clone nanobind-bazel to your machine,
and then declare it as a ``local_path_override()`` dependency:

.. code-block:: python

    # MODULE.bazel
    bazel_dep(name = "nanobind_bazel", version = "")
    local_path_override(
        module_name = "nanobind_bazel",
        path = "/path/to/nanobind-bazel/", # replace this with the actual path.
    )

.. note::

    At minimum, Bazel version 7.0.0 is required to use nanobind-bazel.


.. _bazel-build:

Declaring and building nanobind extension targets
-------------------------------------------------

The main tool to build nanobind C++ extensions for your Python bindings is the
:py:func:`nanobind_extension` rule.

Like all public nanobind-bazel APIs, it resides in the ``build_defs`` submodule.
To import it into a BUILD file, use the builtin ``load`` command:

.. code-block:: python

    # In a BUILD file, e.g. my_project/BUILD
    load("@nanobind_bazel//:build_defs.bzl", "nanobind_extension")

    nanobind_extension(
        name = "my_ext",
        srcs = ["my_ext.cpp"],
    )

In this short snippet, a nanobind Python module called ``my_ext`` is declared,
with its contents coming from the C++ source file of the same name.
Conveniently, only the actual module name must be declared - its place in your
Python project hierarchy is automatically determined by the location of your
build file.

For a comprehensive list of all available build rules in nanobind-bazel, refer
to the rules section in the :ref:`nanobind-bazel API reference <rules-bazel>`.

.. _bazel-stable-abi:

Building against the stable ABI
-------------------------------

As in nanobind's CMake config, you can build bindings targeting Python's
stable ABI, starting from version 3.12. To do this, specify the target
version using the ``@nanobind_bazel//:py-limited-api`` flag. For example,
to build extensions against the CPython 3.12 stable ABI, pass the option
``@nanobind_bazel//:py-limited-api="cp312"`` to your ``bazel build`` command.

For more information about available flags, refer to the flags section in the
:ref:`nanobind-bazel API reference <flags-bazel>`.

Generating stubs for built extensions
-------------------------------------

You can also use Bazel to generate stubs for an extension directly at build
time with the ``nanobind_stubgen`` macro. Here is an example of a nanobind
extension with a stub file generation target declared directly alongside it:

.. code-block:: python

    # Same as before in a BUILD file
    load(
        "@nanobind_bazel//:build_defs.bzl",
        "nanobind_extension",
        "nanobind_stubgen",
    )

    nanobind_extension(
        name = "my_ext",
        srcs = ["my_ext.cpp"],
    )

    nanobind_stubgen(
        name = "my_ext_stubgen",
        module = ":my_ext",
    )

You can then generate stubs on an extension by invoking
``bazel run //my_project:my_ext_stubgen``. Note that this requires actually
running the target instead of only building it via ``bazel build``, since a
Python script needs to be executed for stub generation.

Naturally, since stub generation relies on the given shared object files, the
actual extensions are built in the process before invocation of the stub
generation script.

Building extensions for free-threaded Python
--------------------------------------------

Starting from CPython 3.13, bindings extensions can be built for a free-threaded
CPython interpreter. This requires two things: First, an eligible toolchain must
be defined in your MODULE.bazel file, e.g. like so:

.. code-block:: python

    bazel_dep(name = "rules_python", version = "1.0.0")

    python = use_extension("@rules_python//python/extensions:python.bzl", "python")
    python.toolchain(python_version = "3.13")

And secondly, the ``@rules_python//python/config_settings:py_freethreaded`` flag must
be set to "yes" when building your nanobind extension target, e.g. as
``bazel build //path/to:my_ext --@rules_python//python/config_settings:py_freethreaded=yes``.

Then, ``rules_python`` will bootstrap a free-threaded version of your target interpreter,
and ``nanobind_bazel`` will define the ``NB_FREE_THREADED`` macro for the libnanobind
build, indicating that nanobind should be built with free-threading support.
For a comprehensive overview on nanobind with free-threaded Python, refer to the
:ref:`free-threading documentation <free-threaded>`.

nanobind-bazel and Python packaging
-----------------------------------

Unlike CMake, which has a variety of projects supporting PEP517-style
Python package builds, Bazel does not currently have a fully featured
PEP517-compliant packaging backend available.

To produce Python wheels containing bindings built with nanobind-bazel,
you have various options, with two of the most prominent strategies being

1. Using a wheel builder script with the facilities provided by a Bazel
support package for Python, such as ``py_binary`` or ``py_wheel`` from
`rules_python <https://github.com/bazelbuild/rules_python/>`__. This is
a lower-level, more complex workflow, but it provides more granular
control of how your Python wheel is built.

2. Building all extensions with Bazel through a subprocess, by extending
a Python build backend such as ``setuptools``. This allows you to stick to
those well-established build tools, like ``setuptools``, at the expense
of more boilerplate Python code and slower build times, since Bazel is
only invoked to build the bindings extensions (and their dependencies).

In general, while the latter method requires less setup and customization,
its drawbacks weigh more severely for large projects with more extensions.

.. note::

    An example of packaging with the mentioned setuptools customization method
    can be found in the
    `nanobind_example <https://github.com/wjakob/nanobind_example/tree/bazel>`__
    repository, specifically, on the ``bazel`` branch. It also contains an
    example of how to customize flag names and set default build options across
    platforms with a ``.bazelrc`` file.