xref: /aosp_15_r20/external/pigweed/pw_build/python.rst (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1.. _module-pw_build-python:
2
3===================
4Python GN Templates
5===================
6.. pigweed-module-subpage::
7   :name: pw_build
8
9The Python build is implemented with GN templates defined in
10``pw_build/python.gni``. See the .gni file for complete usage documentation.
11
12.. seealso::
13
14   - :bdg-ref-primary-line:`docs-python-build` for an overview on how Python in
15     GN is built.
16   - The :bdg-ref-primary-line:`module-pw_build` docs for other GN templates
17     available within Pigweed.
18
19.. _module-pw_build-python-base-templates:
20
21---------------------
22Python Base Templates
23---------------------
24The core subset of templates where you can create Python packages, actions,
25scripts and group them together are listed below.
26
27- :ref:`module-pw_build-pw_python_package`
28- :ref:`module-pw_build-pw_python_action`
29- :ref:`module-pw_build-pw_python_script`
30- :ref:`module-pw_build-pw_python_group`
31
32.. _module-pw_build-pw_python_package:
33
34pw_python_package
35=================
36The main Python template is ``pw_python_package``. Each ``pw_python_package``
37target represents a Python package. As described in
38:ref:`module-pw_build-python-target`, each ``pw_python_package`` expands to
39several subtargets. In summary, these are:
40
41- ``<name>`` - Represents the files themselves
42- ``<name>.lint`` - Runs static analysis
43- ``<name>.tests`` - Runs all tests for this package
44- ``<name>.install`` - Installs the package
45- ``<name>.wheel`` - Builds a Python wheel
46
47GN permits using abbreviated labels when the target name matches the directory
48name (e.g. ``//foo`` for ``//foo:foo``). For consistency with this, Python
49package subtargets are aliased to the directory when the target name is the
50same as the directory. For example, these two labels are equivalent:
51
52.. code-block::
53
54   //path/to/my_python_package:my_python_package.tests
55   //path/to/my_python_package:tests
56
57The actions in a ``pw_python_package`` (e.g. installing packages and running
58Pylint) are done within a single GN toolchain to avoid duplication in
59multi-toolchain builds. This toolchain can be set with the
60``pw_build_PYTHON_TOOLCHAIN`` GN arg, which defaults to
61``$dir_pw_build/python_toolchain:python``.
62
63.. note::
64
65   By default, ``<name>.lint`` and ``<name>.tests`` will transitively test and
66   lint any dependencies. This is done for backwards compatibility, but if you
67   don't rely on this behavior you should turn this off by adding
68   ``pw_build_TEST_TRANSITIVE_PYTHON_DEPS = false`` to your project's ``.gn``
69   file.
70
71Arguments
72---------
73- ``setup`` - List of setup file paths (setup.cfg and pyproject.toml), which
74  must all be in the same directory.
75- ``generate_setup``: As an alternative to ``setup``, generate setup files with
76  the keywords in this scope. ``name`` is required. This follows the same
77  structure as a ``setup.cfg`` file's `declarative config
78  <https://setuptools.readthedocs.io/en/latest/userguide/declarative_config.html>`_
79  For example:
80
81  .. code-block::
82
83     generate_setup = {
84       metadata = {
85         name = "a_nifty_package"
86         version = "1.2a"
87       }
88       options = {
89         install_requires = [ "a_pip_package" ]
90       }
91     }
92
93- ``sources`` - Python sources files in the package.
94- ``tests`` - Test files for this Python package.
95
96  .. tip::
97     It is best to keep these files within the same folder as the ``BUILD.gn``
98     and not nested within another folder that contains an ``__init__.py``
99     file. That could cause your tests to be included within the package
100     distributions (See :ref:`module-pw_build-pw_python_distribution`). For
101     example pip installed into the bootstrapped Python virtual environment or
102     as part of a Python wheel.
103
104     If you need to nest your test source files under a sub-folder exclude it in
105     the ``setup.cfg`` file with:
106
107     .. code-block:: cfg
108
109        [options]
110        packages = find:
111
112        # Exclude the tests and test_scripts folders.
113        [options.packages.find]
114        exclude =
115            tests
116            test_scripts
117
118- ``python_deps`` - Dependencies on other pw_python_packages in the GN build.
119- ``python_test_deps`` - Test-only pw_python_package dependencies.
120- ``other_deps`` - Dependencies on GN targets that are not pw_python_packages.
121- ``inputs`` - Other files to track, such as package_data.
122- ``proto_library`` - A pw_proto_library target to embed in this Python package.
123  ``generate_setup`` is required in place of setup if proto_library is used. See
124  :ref:`module-pw_protobuf_compiler-add-to-python-package`.
125- ``static_analysis`` List of static analysis tools to run; ``"*"`` (default)
126  runs all tools. The supported tools are ``"mypy"`` and ``"pylint"``.
127- ``pylintrc`` - Optional path to a pylintrc configuration file to use. If not
128  provided, Pylint's default rcfile search is used. Pylint is executed
129  from the package's setup directory, so pylintrc files in that directory
130  will take precedence over others.
131- ``mypy_ini`` - Optional path to a mypy configuration file to use. If not
132  provided, mypy's default configuration file search is used. mypy is
133  executed from the package's setup directory, so mypy.ini files in that
134  directory will take precedence over others.
135
136Example
137-------
138This is an example Python package declaration for a ``pw_my_module`` module.
139
140.. code-block::
141
142   import("//build_overrides/pigweed.gni")
143
144   import("$dir_pw_build/python.gni")
145
146   pw_python_package("py") {
147     setup = [
148       "pyproject.toml",
149       "setup.cfg",
150     ]
151     sources = [
152       "pw_my_module/__init__.py",
153       "pw_my_module/alfa.py",
154       "pw_my_module/bravo.py",
155       "pw_my_module/charlie.py",
156     ]
157     tests = [
158       "alfa_test.py",
159       "charlie_test.py",
160     ]
161     python_deps = [
162       "$dir_pw_status/py",
163       ":some_protos.python",
164     ]
165     python_test_deps = [ "$dir_pw_build/py" ]
166     pylintrc = "$dir_pigweed/.pylintrc"
167   }
168
169.. _module-pw_build-pw_python_action:
170
171pw_python_action
172================
173The ``pw_python_action`` template is a convenience wrapper around GN's `action
174function <https://gn.googlesource.com/gn/+/main/docs/reference.md#func_action>`_
175for running Python scripts. See
176:bdg-ref-primary-line:`module-pw_build-python-action` in the ``pw_build``
177documentation for usage.
178
179.. _module-pw_build-pw_python_script:
180
181pw_python_script
182================
183A ``pw_python_script`` represents a set of standalone Python scripts and/or
184tests. These files support all of the arguments of ``pw_python_package`` except
185those ``setup``. These targets can be installed, but this only installs their
186dependencies.
187
188``pw_python_script`` allows creating a
189:ref:`pw_python_action <module-pw_build-python-action>` associated with the
190script. To create an action, pass an ``action`` scope to ``pw_python_script``.
191If there is only a single source file, it serves as the action's ``script`` by
192default.
193
194An action in ``pw_python_script`` can always be replaced with a standalone
195``pw_python_action``, but using the embedded action has some advantages:
196
197- The embedded action target bridges the gap between actions and Python targets.
198  A Python script can be expressed in a single, concise GN target, rather than
199  in two overlapping, dependent targets.
200- The action automatically depends on the ``pw_python_script``. This ensures
201  that the script's dependencies are installed and the action automatically
202  reruns when the script's sources change, without needing to specify a
203  dependency, a step which is easy to forget.
204- Using a ``pw_python_script`` with an embedded action is a simple way to check
205  an existing action's script with Pylint or Mypy or to add tests.
206
207.. _module-pw_build-pw_python_group:
208
209pw_python_group
210===============
211Represents a group of ``pw_python_package`` and ``pw_python_script`` targets.
212These targets do not add any files. Their subtargets simply forward to those of
213their dependencies.
214
215.. code-block::
216
217   pw_python_group("solar_system_python_packages") {
218     python_deps = [
219       "//planets/mercury/py",
220       "//planets/venus/py",
221       "//planets/earth/py",
222       "//planets/mars/py",
223       "//planets/jupiter/py",
224       "//planets/saturn/py",
225       "//planets/uranus/py",
226       "//planets/neptune/py",
227       "//planetoids/ceres/py",
228       "//planetoids/pluto/py",
229     ]
230   }
231
232----------------------------
233Python Environment Templates
234----------------------------
235Templates that manage the Python build and bootstrap environment are listed
236here.
237
238- :ref:`module-pw_build-pw_python_venv`
239- :ref:`module-pw_build-pw_python_pip_install`
240
241.. _module-pw_build-pw_python_venv:
242
243pw_python_venv
244==============
245Defines and creates a Python virtualenv. This template is used by Pigweed in
246https://cs.pigweed.dev/pigweed/+/main:pw_env_setup/BUILD.gn to create a
247virtualenv for use within the GN build that all Python actions will run in.
248
249Example
250-------
251.. code-block::
252   :caption: Example of a typical Python venv definition in a top level
253             :octicon:`file;1em` ``BUILD.gn``
254
255   declare_args() {
256     pw_build_PYTHON_BUILD_VENV = "//:my_build_venv"
257   }
258
259   pw_python_group("my_product_packages") {
260     python_deps = [
261       "//product_dev_tools/py",
262       "//product_release_tools/py",
263     ]
264   }
265
266   pw_python_venv("my_build_venv") {
267     path = "$root_build_dir/python-build-venv"
268     constraints = [ "//tools/constraints.list" ]
269     requirements = [ "//tools/requirements.txt" ]
270     source_packages = [
271       "$dir_pw_env_setup:core_pigweed_python_packages",
272       "//tools:another_pw_python_package",
273       "//:my_product_packages",
274     ]
275     pip_generate_hashes = true
276   }
277
278Arguments
279---------
280- ``path``: The directory where the virtualenv will be created. This is relative
281  to the GN root and must begin with "$root_build_dir/" if it lives in the
282  output directory or "//" if it lives in elsewhere.
283
284- ``constraints``: A list of constraint files used when performing pip install
285  into this virtualenv. By default this is set to the
286  ``pw_build_PIP_CONSTRAINTS`` GN arg.
287
288- ``requirements``: A list of requirements files to install into this virtualenv
289  on creation. By default this is set to the ``pw_build_PIP_REQUIREMENTS`` GN
290  arg.
291
292  .. seealso::
293
294     For more info on the ``pw_build_PIP_CONSTRAINTS`` and
295     ``pw_build_PIP_REQUIREMENTS`` GN args see:
296     :ref:`docs-python-build-python-gn-requirements-files`
297
298- ``pip_generate_hashes``: (Default: false) Use ``--generate-hashes`` When
299  running ``pip-compile <A list of requirements files to install into this
300  virtualenv>`` to compute the final ``requirements.txt``
301
302- ``source_packages``: A list of in-tree
303  :ref:`module-pw_build-pw_python_package` or targets that will be checked for
304  external third_party pip dependencies to install into this
305  virtualenv. Note this list of targets isn't actually installed into the
306  virtualenv. Only packages defined inside the ``[options] install_requires``
307  section of each pw_python_package's setup.cfg will be pip installed.
308
309  .. seealso::
310
311     For an example ``setup.cfg`` file see: `Configuring setuptools using
312     setup.cfg files
313     <https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`_
314
315- ``output_logs``: (Default: true) If this is true then the virtual environment will output to logs.
316
317.. _module-pw_build-pw_python_pip_install:
318
319pw_python_pip_install
320=====================
321This will pip install ``pw_python_package`` targets into the bootstrapped
322developer environment.
323
324Example
325-------
326.. code-block::
327   :caption: Example of a typical Python venv definition in a top level
328             :octicon:`file;1em` ``BUILD.gn``
329
330   pw_python_pip_install("pip_install_my_product_packages") {
331     packages = [
332       "//product_dev_tools/py",
333       "//product_release_tools/py",
334     ]
335   }
336
337Arguments
338---------
339
340- ``packages``: A list of :ref:`module-pw_build-pw_python_package` targets to be
341  pip installed.  All packages specified will be installed using a single ``pip
342  install`` command with a ``--constraint`` argument for each constraint file in
343  the ``pw_build_PIP_CONSTRAINTS`` GN arg.
344
345- ``editable``: If true, --editable is passed to the pip install command.
346
347- ``force_reinstall``: If true, ``--force-reinstall`` is passed to the pip
348  install command.
349
350.. _module-pw_build-python-dist:
351
352------------------------------
353Python Distributable Templates
354------------------------------
355Pigweed also provides some templates to make it easier to bundle Python packages
356for deployment. These templates are found in ``pw_build/python_dist.gni``.
357
358- :ref:`module-pw_build-pw_python_wheels`
359- :ref:`module-pw_build-pw_python_zip_with_setup`
360- :ref:`module-pw_build-pw_python_distribution`
361
362.. _module-pw_build-pw_python_wheels:
363
364pw_python_wheels
365================
366Collects Python wheels for one or more ``pw_python_package`` targets, plus any
367additional ``pw_python_package`` targets they depend on, directly or indirectly.
368Note that this does not include Python dependencies that come from outside the
369GN build, like packages from PyPI, for example. Those should still be declared
370in the package's ``setup.cfg`` file as usual.
371
372Arguments
373---------
374- ``packages`` - List of ``pw_python_package`` targets whose wheels should be
375  included; their dependencies will be pulled in as wheels also.
376- ``directory`` - Output directory for the collected wheels. Defaults to
377  ``$target_out_dir/$target_name``.
378
379Wheel collection under the hood
380-------------------------------
381The ``.wheel`` subtarget of every ``pw_python_package`` generates a wheel
382(``.whl``) for the Python package. The ``pw_python_wheels`` template figures
383out which wheels to collect by traversing the ``pw_python_package_wheels``
384`GN metadata
385<https://gn.googlesource.com/gn/+/HEAD/docs/reference.md#var_metadata>`_ key,
386which lists the output directory for each wheel.
387
388.. _module-pw_build-pw_python_zip_with_setup:
389
390pw_python_zip_with_setup
391========================
392Generates a ``.zip`` archive suitable for deployment outside of the project's
393developer environment. The generated ``.zip`` contains Python wheels
394(``.whl`` files) for one or more ``pw_python_package`` targets, plus wheels for
395any additional ``pw_python_package`` targets in the GN build they depend on,
396directly or indirectly. Dependencies from outside the GN build, such as packages
397from PyPI, must be listed in packages' ``setup.cfg`` file as usual.
398
399The ``.zip`` also includes simple setup scripts for Linux,
400MacOS, and Windows. The setup scripts automatically create a Python virtual
401environment and install the whole collection of wheels into it using ``pip``.
402
403Optionally, additional files and directories can be included in the archive.
404One common example of an additional file to include is a README file with setup
405and usage instructions for the distributable. A simple ready-to-use README file
406is available at ``pw_build/py_dist/README.md``.
407
408Arguments
409---------
410- ``packages`` - A list of `pw_python_package` targets whose wheels should be
411  included; their dependencies will be pulled in as wheels also.
412- ``inputs`` - An optional list of extra files to include in the generated
413  ``.zip``, formatted the same way as the ``inputs`` argument to ``pw_zip``
414  targets.
415- ``dirs`` - An optional list of directories to include in the generated
416  ``.zip``, formatted the same was as the ``dirs`` argument to ``pw_zip``
417  targets.
418
419Example
420-------
421
422.. code-block::
423
424   import("//build_overrides/pigweed.gni")
425
426   import("$dir_pw_build/python_dist.gni")
427
428   pw_python_zip_with_setup("my_tools") {
429     packages = [ ":some_python_package" ]
430     inputs = [ "$dir_pw_build/python_dist/README.md > /${target_name}/" ]
431   }
432
433.. _module-pw_build-pw_python_distribution:
434
435pw_python_distribution
436======================
437Generates a directory of Python packages from source files suitable for
438deployment outside of the project developer environment. The resulting directory
439contains only files mentioned in each package's ``setup.cfg`` file. This is
440useful for bundling multiple Python packages up into a single package for
441distribution to other locations like `<http://pypi.org>`_.
442
443Arguments
444---------
445
446- ``packages`` - A list of :ref:`module-pw_build-pw_python_package` targets to be installed into
447  the build directory. Their dependencies will be pulled in as wheels also.
448
449- ``include_tests`` - If true, copy Python package tests to a ``tests`` subdir.
450
451- ``extra_files`` - A list of extra files that should be included in the output.
452  The format of each item in this list follows this convention:
453
454  .. code-block:: text
455
456     //some/nested/source_file > nested/destination_file
457
458  - Source and destination file should be separated by ``>``.
459
460  - The source file should be a GN target label (starting with ``//``).
461
462  - The destination file will be relative to the generated output
463    directory. Parent directories are automatically created for each file. If a
464    file would be overwritten an error is raised.
465
466- ``generate_setup_cfg`` - If included, create a merged ``setup.cfg`` for all
467  python Packages using either a ``common_config_file`` as a base or ``name``
468  and ``version`` strings. The ``common_config_file`` should contain the
469  required fields in the ``metadata`` and ``options`` sections as shown in
470  `Configuring setup() using setup.cfg files <https://setuptools.pypa.io/en/latest/userguide/declarative_config.html>`_.
471
472  This scope can optionally include:
473
474  - ``append_git_sha_to_version = true``: Append the current git SHA to the
475    package version string after a ``+`` sign.
476
477  - ``append_date_to_version = true``: Append the current date to the package
478    version string after a ``+`` sign.
479
480  - ``include_default_pyproject_file = true``: Include a standard
481    ``pyproject.toml`` file in the output.
482
483  - ``include_extra_files_in_package_data = true``: Add any ``extra_files``
484    entries to the generated ``setup.cfg`` file under the
485    ``[options.package_data]`` section.
486
487  - ``auto_create_package_data_init_py_files = true``: (Default: true) Create
488    ``__init__.py`` files as needed in all subdirs of ``extra_files`` when
489    including in ``[options.package_data]``.
490
491  .. code-block::
492     :caption: :octicon:`file;1em` Example using a common setup.cfg and
493               pyproject.toml files.
494
495     generate_setup_cfg = {
496       common_config_file = "pypi_common_setup.cfg"
497       append_date_to_version = true
498     }
499     extra_files = [
500       "//source/pyproject.toml > pyproject.toml"
501     ]
502
503  .. code-block::
504     :caption: :octicon:`file;1em` Example using name and version strings and a
505               default pyproject.toml file.
506
507     generate_setup_cfg = {
508       name = "awesome"
509       version = "1.0.0"
510       include_default_pyproject_file = true
511       append_date_to_version = true
512     }
513
514Using this template will create an additional target for and building a Python
515wheel. For example if you define ``pw_python_distribution("awesome")`` the
516resulting targets that get created will be:
517
518- ``awesome`` - This will create the merged package with all source files in
519  place in the out directory under ``out/obj/awesome/``.
520- ``awesome.wheel`` - This builds a Python wheel from the above source files
521  under ``out/obj/awesome._build_wheel/awesome*.whl``.
522
523Example
524-------
525
526.. code-block::
527   :caption: :octicon:`file;1em` ./pw_env_setup/BUILD.gn
528
529   import("//build_overrides/pigweed.gni")
530
531   import("$dir_pw_build/python_dist.gni")
532
533   pw_python_distribution("build_python_source_tree") {
534     packages = [
535       ":some_python_package",
536       ":another_python_package",
537     ]
538     include_tests = true
539     extra_files = [
540       "//README.md > ./README.md",
541       "//some_python_package/py/BUILD.bazel > some_python_package/BUILD.bazel",
542       "//another_python_package/py/BUILD.bazel > another_python_package/BUILD.bazel",
543     ]
544     generate_setup_cfg = {
545       common_config_file = "pypi_common_setup.cfg"
546       append_git_sha_to_version = true
547       append_date_to_version = true
548     }
549   }
550
551
552.. code-block:: text
553   :caption: :octicon:`file-directory;1em`
554             ./out/obj/pw_env_setup/build_python_source_tree/
555
556   $ tree ./out/obj/pw_env_setup/build_python_source_tree/
557   ├── README.md
558   ├── setup.cfg
559   ├── some_python_package
560   │   ├── BUILD.bazel
561   │   ├── __init__.py
562   │   ├── py.typed
563   │   ├── some_source_file.py
564   │   └── tests
565   │       └── some_source_test.py
566   └── another_python_package
567       ├── BUILD.bazel
568       ├── __init__.py
569       ├── another_source_file.py
570       ├── py.typed
571       └── tests
572           └── another_source_test.py
573