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