679 lines
26 KiB
ReStructuredText
679 lines
26 KiB
ReStructuredText
.. _docs-python-build:
|
|
|
|
=========================
|
|
Pigweed's GN Python Build
|
|
=========================
|
|
|
|
.. seealso::
|
|
|
|
- :bdg-ref-primary-line:`module-pw_build-python` for detailed template usage.
|
|
- :bdg-ref-primary-line:`module-pw_build` for other GN templates available
|
|
within Pigweed.
|
|
- :bdg-ref-primary-line:`docs-build-system` for a high level guide and
|
|
background information on Pigweed's build system as a whole.
|
|
|
|
Pigweed uses a custom GN-based build system to manage its Python code. The
|
|
Pigweed Python build supports packaging, installation and distribution of
|
|
interdependent local Python packages. It also provides for fast, incremental
|
|
static analysis and test running suitable for live use during development (e.g.
|
|
with :ref:`module-pw_watch`) or in continuous integration.
|
|
|
|
Pigweed's Python code is exclusively managed by GN, but the GN-based build may
|
|
be used alongside CMake, Bazel, or any other build system. Pigweed's environment
|
|
setup uses GN to set up the initial Python environment, regardless of the final
|
|
build system. As needed, non-GN projects can declare just their Python packages
|
|
in GN.
|
|
|
|
How it Works
|
|
============
|
|
In addition to compiler commands a Pigweed GN build will execute Python scripts
|
|
for various reasons including running tests, linting code, generating protos and
|
|
more. All these scripts are run as part of a
|
|
:ref:`module-pw_build-pw_python_action` GN template which will ultimately run
|
|
``python``. Running Python on it's own by default will make any Python packages
|
|
installed on the users system available for importing. This is not good and can
|
|
lead to flaky builds when different packages are installed on each developer
|
|
workstation. To get around this the Python community uses `virtual environments
|
|
<https://docs.python.org/3/library/venv.html>`_ (venvs) that expose a specific
|
|
set of Python packages separate from the host system.
|
|
|
|
When a Pigweed GN build starts a single venv is created for use by all
|
|
:ref:`pw_python_actions <module-pw_build-pw_python_action>` throughout the build
|
|
graph. Once created, all required third-party Python packages needed for the
|
|
project are installed. At that point no further modifications are made to
|
|
the venv. Of course if a new third-party package dependency is added it will be
|
|
installed too. Beyond that all venvs remain static. More venvs can be created
|
|
with the :ref:`module-pw_build-pw_python_venv` template if desired, but only one
|
|
is used by default.
|
|
|
|
.. card::
|
|
|
|
**Every pw_python_action is run inside a venv**
|
|
^^^
|
|
.. mermaid::
|
|
:caption:
|
|
|
|
flowchart LR
|
|
out[GN Build Dir<br/>fa:fa-folder out]
|
|
|
|
out -->|ninja -C out| createvenvs
|
|
|
|
createvenvs(Create venvs)
|
|
createvenvs --> pyactions1
|
|
createvenvs --> pyactions2
|
|
|
|
subgraph pyactions1[Python venv 1]
|
|
direction TB
|
|
venv1(fa:fa-folder out/python-venv  )
|
|
a1["pw_python_action('one')"]
|
|
a2["pw_python_action('two')"]
|
|
venv1 --> a1
|
|
venv1 --> a2
|
|
end
|
|
|
|
subgraph pyactions2[Python venv 2]
|
|
direction TB
|
|
venv2(fa:fa-folder out/another-venv  )
|
|
a3["pw_python_action('three')"]
|
|
a4["pw_python_action('four')"]
|
|
venv2 --> a3
|
|
venv2 --> a4
|
|
end
|
|
|
|
.. note::
|
|
|
|
Pigweed uses `this venv target
|
|
<https://cs.opensource.google/pigweed/pigweed/+/main:pw_env_setup/BUILD.gn?q=pigweed_build_venv>`_
|
|
if a project does not specify it's own build venv. See
|
|
:bdg-ref-primary-line:`docs-python-build-python-gn-venv` on how to define
|
|
your own default venv.
|
|
|
|
Having a static venv containing only third-party dependencies opens the flood
|
|
gates for python scripts to run. If the venv only contains third-party
|
|
dependencies you may be wondering how you can import your own in-tree Python
|
|
packages. Python code run in the build may still import any in-tree Python
|
|
packages created with :ref:`module-pw_build-pw_python_package`
|
|
templates. However this only works if a correct ``python_deps`` arg is
|
|
provided. Having that Python dependency defined in GN allows the
|
|
:ref:`module-pw_build-pw_python_action`
|
|
to set `PYTHONPATH
|
|
<https://docs.python.org/3/using/cmdline.html#envvar-PYTHONPATH>`_ so that given
|
|
package can be imported. This has the benefit of the build failing if a
|
|
dependency for any Python action or package is missing.
|
|
|
|
.. admonition:: Benefits of Python ``venvs`` in GN
|
|
:class: important
|
|
|
|
- Using venvs to execute Python in GN provides reproducible builds with fixed
|
|
third-party dependencies.
|
|
- Using ``PYTHONPATH`` coupled with ``python_deps`` to import in-tree Python
|
|
packages enforces dependency correctness.
|
|
|
|
|
|
Managing Python Requirements
|
|
============================
|
|
|
|
.. _docs-python-build-python-gn-venv:
|
|
|
|
Build Time Python Virtualenv
|
|
----------------------------
|
|
Pigweed's GN Python build infrastructure relies on a single build-only venv for
|
|
executing Python code. This provides an isolated environment with a reproducible
|
|
set of third party Python constraints where all Python tests and linting can
|
|
run. All :ref:`module-pw_build-pw_python_action` targets are executed within
|
|
this build venv.
|
|
|
|
The default build venv is specified via a GN arg and is best set in the root
|
|
``.gn`` or ``BUILD.gn`` file. For example:
|
|
|
|
.. code-block::
|
|
|
|
pw_build_PYTHON_BUILD_VENV = "//:project_build_venv"
|
|
|
|
.. _docs-python-build-python-gn-requirements-files:
|
|
|
|
Third-party Python Requirements and Constraints
|
|
-----------------------------------------------
|
|
Your project may have third party Python dependencies you wish to install into
|
|
the bootstrapped environment and in the GN build venv. There are two main ways
|
|
to add Python package dependencies:
|
|
|
|
**Adding Requirements Files**
|
|
|
|
1. Add a ``install_requires`` entry to a ``setup.cfg`` file defined in a
|
|
:ref:`module-pw_build-pw_python_package` template. This is the best option
|
|
if your in-tree Python package requires an external Python package.
|
|
|
|
2. Create a standard Python ``requirements.txt`` file in your project and add it
|
|
to the ``pw_build_PIP_REQUIREMENTS`` GN arg list.
|
|
|
|
Requirements files support a wide range of install locations including
|
|
packages from pypi.org, the local file system and git repos. See `pip's
|
|
Requirements File documentation
|
|
<https://pip.pypa.io/en/stable/user_guide/#requirements-files>`_ for more
|
|
info.
|
|
|
|
The GN arg can be set in your project's root ``.gn`` or ``BUILD.gn`` file.
|
|
|
|
.. code-block::
|
|
|
|
pw_build_PIP_REQUIREMENTS = [
|
|
# Project specific requirements
|
|
"//tools/requirements.txt",
|
|
]
|
|
|
|
See the :ref:`docs-python-build-python-gn-structure` section below for a full
|
|
code listing.
|
|
|
|
**Adding Constraints Files**
|
|
|
|
Every project should ideally inherit Pigweed's third party Python package
|
|
version. This is accomplished via `Python constraints files
|
|
<https://pip.pypa.io/en/stable/user_guide/#constraints-files>`_. Constraints
|
|
control which versions of packages get installed by ``pip`` if that package is
|
|
installed. To inherit Pigweed's Python constraints include ``constraint.list``
|
|
from the ``pw_env_setup`` module from in your top level ``.gn`` file. Additonal
|
|
project specific constraints can be appended to this list.
|
|
|
|
.. code-block::
|
|
|
|
pw_build_PIP_CONSTRAINTS = [
|
|
"$dir_pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list",
|
|
"//tools/constraints.txt",
|
|
]
|
|
|
|
.. _docs-python-build-python-gn-structure:
|
|
|
|
GN Structure for Python Code
|
|
============================
|
|
Here is a full example of what is required to build Python packages using
|
|
Pigweed's GN build system. A brief file hierarchy is shown here with file
|
|
content following. See also :ref:`docs-python-build-structure` below for details
|
|
on the structure of Python packages.
|
|
|
|
.. code-block::
|
|
:caption: :octicon:`file-directory;1em` Top level GN file hierarchy
|
|
:name: gn-python-file-tree
|
|
|
|
project_root/
|
|
├── .gn
|
|
├── BUILDCONFIG.gn
|
|
├── build_overrides/
|
|
│ └── pigweed.gni
|
|
├── BUILD.gn
|
|
│
|
|
├── python_package1/
|
|
│ ├── BUILD.gn
|
|
│ ├── setup.cfg
|
|
│ ├── setup.py
|
|
│ ├── pyproject.toml
|
|
│ │
|
|
│ ├── package_name/
|
|
│ │ ├── module_a.py
|
|
│ │ ├── module_b.py
|
|
│ │ ├── py.typed
|
|
│ │ └── nested_package/
|
|
│ │ ├── py.typed
|
|
│ │ └── module_c.py
|
|
│ │
|
|
│ ├── module_a_test.py
|
|
│ └── module_c_test.py
|
|
│
|
|
├── third_party/
|
|
│ └── pigweed/
|
|
│
|
|
└── ...
|
|
|
|
- :octicon:`file-directory;1em` project_root/
|
|
|
|
- :octicon:`file;1em` .gn
|
|
|
|
.. code-block::
|
|
|
|
buildconfig = "//BUILDCONFIG.gn"
|
|
import("//build_overrides/pigweed.gni")
|
|
|
|
default_args = {
|
|
pw_build_PIP_CONSTRAINTS = [
|
|
# Inherit Pigweed Python constraints
|
|
"$dir_pw_env_setup/py/pw_env_setup/virtualenv_setup/constraint.list",
|
|
|
|
# Project specific constraints file
|
|
"//tools/constraint.txt",
|
|
]
|
|
|
|
pw_build_PIP_REQUIREMENTS = [
|
|
# Project specific requirements
|
|
"//tools/requirements.txt",
|
|
]
|
|
|
|
# Default gn build virtualenv target.
|
|
pw_build_PYTHON_BUILD_VENV = "//:project_build_venv"
|
|
}
|
|
|
|
- :octicon:`file;1em` BUILDCONFIG.gn
|
|
|
|
.. code-block::
|
|
|
|
_pigweed_directory = {
|
|
import("//build_overrides/pigweed.gni")
|
|
}
|
|
|
|
set_default_toolchain("${_pigweed_directory.dir_pw_toolchain}/default")
|
|
|
|
- :octicon:`file-directory;1em` build_overrides / :octicon:`file;1em` pigweed.gni
|
|
|
|
.. code-block::
|
|
|
|
declare_args() {
|
|
# Location of the Pigweed repository.
|
|
dir_pigweed = "//third_party/pigweed/"
|
|
}
|
|
|
|
# Upstream Pigweed modules.
|
|
import("$dir_pigweed/modules.gni")
|
|
|
|
- :octicon:`file;1em` BUILD.gn
|
|
|
|
.. code-block::
|
|
|
|
import("//build_overrides/pigweed.gni")
|
|
|
|
import("$dir_pw_build/python.gni")
|
|
import("$dir_pw_build/python_dist.gni")
|
|
import("$dir_pw_build/python_venv.gni")
|
|
import("$dir_pw_unit_test/test.gni")
|
|
|
|
# Lists all the targets build by default with e.g. `ninja -C out`.
|
|
group("default") {
|
|
deps = [
|
|
":python.lint",
|
|
":python.tests",
|
|
]
|
|
}
|
|
|
|
# This group is built during bootstrap to setup the interactive Python
|
|
# environment.
|
|
pw_python_group("python") {
|
|
python_deps = [
|
|
# Generate and pip install _all_python_packages
|
|
":pip_install_project_tools",
|
|
]
|
|
}
|
|
|
|
# In-tree Python packages
|
|
_project_python_packages = [
|
|
"//python_package1",
|
|
]
|
|
|
|
# Pigweed Python packages to include
|
|
_pigweed_python_packages = [
|
|
"$dir_pw_env_setup:core_pigweed_python_packages",
|
|
"$dir_pigweed/targets/lm3s6965evb_qemu/py",
|
|
"$dir_pigweed/targets/stm32f429i_disc1/py",
|
|
]
|
|
|
|
_all_python_packages =
|
|
_project_python_packages + _pigweed_python_packages
|
|
|
|
# The default venv for Python actions in GN
|
|
# Set this gn arg in a declare_args block in this file 'BUILD.gn' or in '.gn' to
|
|
# use this venv.
|
|
#
|
|
# pw_build_PYTHON_BUILD_VENV = "//:project_build_venv"
|
|
#
|
|
pw_python_venv("project_build_venv") {
|
|
path = "$root_build_dir/python-venv"
|
|
constraints = pw_build_PIP_CONSTRAINTS
|
|
requirements = pw_build_PIP_REQUIREMENTS
|
|
|
|
# Ensure all third party Python dependencies are installed into this venv.
|
|
# This works by checking the setup.cfg files for all packages listed here and
|
|
# installing the packages listed in the [options].install_requires field.
|
|
source_packages = _all_python_packages
|
|
}
|
|
|
|
# This template collects all python packages and their dependencies into a
|
|
# single super Python package for installation into the bootstrapped virtual
|
|
# environment.
|
|
pw_python_distribution("generate_project_python_distribution") {
|
|
packages = _all_python_packages
|
|
generate_setup_cfg = {
|
|
name = "project-tools"
|
|
version = "0.0.1"
|
|
append_date_to_version = true
|
|
include_default_pyproject_file = true
|
|
}
|
|
}
|
|
|
|
# Install the project-tools super Python package into the bootstrapped
|
|
# Python venv.
|
|
pw_python_pip_install("pip_install_project_tools") {
|
|
packages = [ ":generate_project_python_distribution" ]
|
|
}
|
|
|
|
.. _docs-python-build-structure:
|
|
|
|
Pigweed Module Structure for Python Code
|
|
========================================
|
|
Pigweed Python code is structured into standard Python packages. This makes it
|
|
simple to package and distribute Pigweed Python packages with common Python
|
|
tools.
|
|
|
|
Like all Pigweed source code, Python packages are organized into Pigweed
|
|
modules. A module's Python package is nested under a ``py/`` directory (see
|
|
:ref:`Pigweed Module Stucture <docs-module-structure>`).
|
|
|
|
.. code-block::
|
|
:caption: :octicon:`file-directory;1em` Example layout of a Pigweed Python package.
|
|
:name: python-file-tree
|
|
|
|
module_name/
|
|
├── py/
|
|
│ ├── BUILD.gn
|
|
│ ├── setup.cfg
|
|
│ ├── setup.py
|
|
│ ├── pyproject.toml
|
|
│ ├── package_name/
|
|
│ │ ├── module_a.py
|
|
│ │ ├── module_b.py
|
|
│ │ ├── py.typed
|
|
│ │ └── nested_package/
|
|
│ │ ├── py.typed
|
|
│ │ └── module_c.py
|
|
│ ├── module_a_test.py
|
|
│ └── module_c_test.py
|
|
└── ...
|
|
|
|
The ``BUILD.gn`` declares this package in GN. For upstream Pigweed, a presubmit
|
|
check in ensures that all Python files are listed in a ``BUILD.gn``.
|
|
|
|
Pigweed prefers to define Python packages using ``setup.cfg`` files. In the
|
|
above file tree ``setup.py`` and ``pyproject.toml`` files are stubs with the
|
|
following content:
|
|
|
|
.. code-block::
|
|
:caption: :octicon:`file;1em` setup.py
|
|
:name: setup-py-stub
|
|
|
|
import setuptools # type: ignore
|
|
setuptools.setup() # Package definition in setup.cfg
|
|
|
|
.. code-block::
|
|
:caption: :octicon:`file;1em` pyproject.toml
|
|
:name: pyproject-toml-stub
|
|
|
|
[build-system]
|
|
requires = ['setuptools', 'wheel']
|
|
build-backend = 'setuptools.build_meta'
|
|
|
|
The stub ``setup.py`` file is there to support running ``pip install --editable``.
|
|
|
|
Each ``pyproject.toml`` file is required to specify which build system should be
|
|
used for the given Python package. In Pigweed's case it always specifies using
|
|
setuptools.
|
|
|
|
.. seealso::
|
|
|
|
- ``setup.cfg`` examples at `Configuring setup() using setup.cfg files`_
|
|
- ``pyproject.toml`` background at `Build System Support - How to use it?`_
|
|
|
|
|
|
.. _module-pw_build-python-target:
|
|
|
|
pw_python_package targets
|
|
-------------------------
|
|
The key abstraction in the Python build is the ``pw_python_package``.
|
|
A ``pw_python_package`` represents a Python package as a GN target. It is
|
|
implemented with a GN template. The ``pw_python_package`` template is documented
|
|
in :ref:`module-pw_build-python`.
|
|
|
|
The key attributes of a ``pw_python_package`` are
|
|
|
|
- a ``setup.py`` file,
|
|
- source files,
|
|
- test files,
|
|
- dependencies on other ``pw_python_package`` targets.
|
|
|
|
A ``pw_python_package`` target is composed of several GN subtargets. Each
|
|
subtarget represents different functionality in the Python build.
|
|
|
|
- ``<name>`` - Represents the Python files in the build, but does not take any
|
|
actions. All subtargets depend on this target.
|
|
- ``<name>.tests`` - Runs all tests for this package.
|
|
|
|
- ``<name>.tests.<test_file>`` - Runs the specified test.
|
|
|
|
- ``<name>.lint`` - Runs static analysis tools on the Python code. This is a
|
|
group of two subtargets:
|
|
|
|
- ``<name>.lint.mypy`` - Runs Mypy on all Python files, if enabled.
|
|
- ``<name>.lint.pylint`` - Runs Pylint on all Python files, if enabled.
|
|
|
|
- ``<name>.install`` - Installs the package in a Python virtual environment.
|
|
- ``<name>.wheel`` - Builds a Python wheel for this package.
|
|
|
|
To avoid unnecessary duplication, all Python actions are executed in the default
|
|
toolchain, even if they are referred to from other toolchains.
|
|
|
|
Testing
|
|
^^^^^^^
|
|
Tests for a Python package are listed in its ``pw_python_package`` target.
|
|
Adding a new test is simple: write the test file and list it in its accompanying
|
|
Python package. The build will run it when the test, the package, or one of its
|
|
dependencies is updated.
|
|
|
|
Static analysis
|
|
^^^^^^^^^^^^^^^
|
|
``pw_python_package`` targets are preconfigured to run Pylint and Mypy on their
|
|
source and test files. Users may specify which ``pylintrc`` and ``mypy.ini``
|
|
files to
|
|
use on a per-package basis. The configuration files may also be provided in the
|
|
directory structure; the tools will locate them using their standard means. Like
|
|
tests, static analysis is only run when files or their dependencies change.
|
|
|
|
Packages may opt out of static analysis as necessary.
|
|
|
|
In addition to user specified ``mypy.ini`` files some arguments are always
|
|
passed to ``mypy`` by default. They can be seen in this excerpt of
|
|
``//pw_build/python.gni`` below:
|
|
|
|
.. literalinclude:: pw_build/python.gni
|
|
:start-after: [default-mypy-args]
|
|
:end-before: [default-mypy-args]
|
|
|
|
Building Python wheels
|
|
^^^^^^^^^^^^^^^^^^^^^^
|
|
`Wheels <https://wheel.readthedocs.io/en/stable/>`_ are the standard format for
|
|
distributing Python packages. The Pigweed Python build supports creating wheels
|
|
for individual packages and groups of packages. Building the ``.wheel``
|
|
subtarget creates a ``.whl`` file for the package using the PyPA's `build
|
|
<https://pypa-build.readthedocs.io/en/stable/>`_ tool.
|
|
|
|
The ``.wheel`` subtarget of any ``pw_python_package`` or
|
|
:ref:`module-pw_build-pw_python_distribution` records the location of the
|
|
generated wheel with `GN metadata
|
|
<https://gn.googlesource.com/gn/+/HEAD/docs/reference.md#var_metadata>`_.
|
|
Wheels for a Python package and its transitive dependencies can be collected
|
|
from the ``pw_python_package_wheels`` key. See
|
|
:ref:`module-pw_build-python-dist`.
|
|
|
|
Protocol buffers
|
|
^^^^^^^^^^^^^^^^
|
|
The Pigweed GN build supports protocol buffers with the ``pw_proto_library``
|
|
target (see :ref:`module-pw_protobuf_compiler`). Python protobuf modules are
|
|
generated as standalone Python packages by default. Protocol buffers may also be
|
|
nested within existing Python packages. In this case, the Python package in the
|
|
source tree is incomplete; the final Python package, including protobufs, is
|
|
generated in the output directory.
|
|
|
|
Generating setup.py
|
|
^^^^^^^^^^^^^^^^^^^
|
|
The ``pw_python_package`` target in the ``BUILD.gn`` duplicates much of the
|
|
information in the ``setup.py`` or ``setup.cfg`` file. In many cases, it would
|
|
be possible to generate a ``setup.py`` file rather than including it in the
|
|
source tree. However, removing the ``setup.py`` would preclude using a direct,
|
|
editable installation from the source tree.
|
|
|
|
Pigweed packages containing protobufs are generated in full or in part. These
|
|
packages may use generated setup files, since they are always packaged or
|
|
installed from the build output directory.
|
|
|
|
|
|
Rationale
|
|
=========
|
|
|
|
Background
|
|
----------
|
|
Developing software involves much more than writing source code. Software needs
|
|
to be compiled, executed, tested, analyzed, packaged, and deployed. As projects
|
|
grow beyond a few files, these tasks become impractical to manage manually.
|
|
Build systems automate these auxiliary tasks of software development, making it
|
|
possible to build larger, more complex systems quickly and robustly.
|
|
|
|
Python is an interpreted language, but it shares most build automation concerns
|
|
with other languages. Pigweed uses Python extensively and must address these
|
|
needs for itself and its users.
|
|
|
|
Existing solutions
|
|
------------------
|
|
The Python programming langauge does not have an official build automation
|
|
system. However, there are numerous Python-focused build automation tools with
|
|
varying degrees of adoption. See the `Python Wiki
|
|
<https://wiki.python.org/moin/ConfigurationAndBuildTools>`_ for examples.
|
|
|
|
A few Python tools have become defacto standards, including `setuptools
|
|
<https://pypi.org/project/setuptools/>`_, `wheel
|
|
<https://pypi.org/project/wheel/>`_, and `pip <https://pypi.org/project/pip/>`_.
|
|
These essential tools address key aspects of Python packaging and distribution,
|
|
but are not intended for general build automation. Tools like `PyBuilder
|
|
<https://pybuilder.io/>`_ and `tox <https://tox.readthedocs.io/en/latest/>`_
|
|
provide more general build automation for Python.
|
|
|
|
The `Bazel <http://bazel.build/>`_ build system has first class support for
|
|
Python and other languages used by Pigweed, including protocol buffers.
|
|
|
|
Challenges
|
|
----------
|
|
Pigweed's use of Python is different from many other projects. Pigweed is a
|
|
multi-language, modular project. It serves both as a library or middleware and
|
|
as a development environment.
|
|
|
|
This section describes Python build automation challenges encountered by
|
|
Pigweed.
|
|
|
|
Dependencies
|
|
^^^^^^^^^^^^
|
|
Pigweed is organized into distinct modules. In Python, each module is a separate
|
|
package, potentially with dependencies on other local or `PyPI
|
|
<https://pypi.org/>`_ packages.
|
|
|
|
The basic Python packaging tools lack dependency tracking for local packages.
|
|
For example, a package's ``setup.py`` or ``setup.cfg`` lists all of
|
|
its dependencies, but ``pip`` is not aware of local packages until they are
|
|
installed. Packages must be installed with their dependencies taken into
|
|
account, in topological sorted order.
|
|
|
|
To work around this, one could set up a private `PyPI server
|
|
<https://pypi.org/project/pypiserver/>`_ instance, but this is too cumbersome
|
|
for daily development and incompatible with editable package installation.
|
|
|
|
Testing
|
|
^^^^^^^
|
|
Tests are crucial to having a healthy, maintainable codebase. While they take
|
|
some initial work to write, the time investment pays for itself many times over
|
|
by contributing to the long-term resilience of a codebase. Despite their
|
|
benefit, developers don't always take the time to write tests. Any barriers to
|
|
writing and running tests result in fewer tests and consequently more fragile,
|
|
bug-prone codebases.
|
|
|
|
There are lots of great Python libraries for testing, such as
|
|
`unittest <https://docs.python.org/3/library/unittest.html>`_ and
|
|
`pytest <https://docs.pytest.org/en/stable/>`_. These tools make it easy to
|
|
write and execute individual Python tests, but are not well suited for managing
|
|
suites of interdependent tests in a large project. Writing a test with these
|
|
utilities does not automatically run them or keep running them as the codebase
|
|
changes.
|
|
|
|
Static analysis
|
|
^^^^^^^^^^^^^^^
|
|
|
|
.. seealso::
|
|
|
|
:bdg-ref-primary-line:`docs-automated-analysis` for info on other static
|
|
analysis tools used in Pigweed.
|
|
|
|
Various static analysis tools exist for Python. Two widely used, powerful tools
|
|
are `Pylint <https://www.pylint.org/>`_ and `Mypy <http://mypy-lang.org/>`_.
|
|
Using these tools improves code quality, as they catch bugs, encourage good
|
|
design practices, and enforce a consistent coding style. As with testing,
|
|
barriers to running static analysis tools cause many developers to skip them.
|
|
Some developers may not even be aware of these tools.
|
|
|
|
Deploying static analysis tools to a codebase like Pigweed has some challenges.
|
|
Mypy and Pylint are simple to run, but they are extremely slow. Ideally, these
|
|
tools would be run constantly during development, but only on files that change.
|
|
These tools do not have built-in support for incremental runs or dependency
|
|
tracking.
|
|
|
|
Another challenge is configuration. Mypy and Pylint support using configuration
|
|
files to select which checks to run and how to apply them. Both tools only
|
|
support using a single configuration file for an entire run, which poses a
|
|
challenge to modular middleware systems where different parts of a project may
|
|
require different configurations.
|
|
|
|
Protocol buffers
|
|
^^^^^^^^^^^^^^^^
|
|
`Protocol buffers <https://developers.google.com/protocol-buffers>`_ are an
|
|
efficient system for serializing structured data. They are widely used by Google
|
|
and other companies.
|
|
|
|
The protobuf compiler ``protoc`` generates Python modules from ``.proto`` files.
|
|
``protoc`` strictly generates protobuf modules according to their directory
|
|
structure. This works well in a monorepo, but poses a challenge to a middleware
|
|
system like Pigweed. Generating protobufs by path also makes integrating
|
|
protobufs with existing packages awkward.
|
|
|
|
Requirements
|
|
------------
|
|
Pigweed aims to provide high quality software components and a fast, effective,
|
|
flexible development experience for its customers. Pigweed's high-level goals
|
|
and the `challenges`_ described above inform these requirements for the Pigweed
|
|
Python build.
|
|
|
|
- Integrate seamlessly with the other Pigweed build tools.
|
|
- Easy to use independently, even if primarily using a different build system.
|
|
- Support standard packaging and distribution with setuptools, wheel, and pip.
|
|
- Correctly manage interdependent local Python packages.
|
|
- Out-of-the-box support for writing and running tests.
|
|
- Preconfigured, trivial-to-run static analysis integration for Pylint and Mypy.
|
|
- Fast, dependency-aware incremental rebuilds and test execution, suitable for
|
|
use with :ref:`module-pw_watch`.
|
|
- Seamless protocol buffer support.
|
|
|
|
Design Decision
|
|
---------------
|
|
Existing Python tools may be effective for Python codebases, but their utility
|
|
is more limited in a multi-language project like Pigweed. The cost of bringing
|
|
up and maintaining an additional build automation system for a single language
|
|
is high.
|
|
|
|
Pigweed uses GN as its primary build system for all languages. While GN does not
|
|
natively support Python, adding support is straightforward with GN templates.
|
|
|
|
GN has strong multi-toolchain and multi-language capabilities. In GN, it is
|
|
straightforward to share targets and artifacts between different languages. For
|
|
example, C++, Go, and Python targets can depend on the same protobuf
|
|
declaration. When using GN for multiple languages, Ninja schedules build steps
|
|
for all languages together, resulting in faster total build times.
|
|
|
|
Not all Pigweed users build with GN. Of Pigweed's three supported build systems,
|
|
GN is the fastest, lightest weight, and easiest to run. It also has simple,
|
|
clean syntax. This makes it feasible to use GN only for Python while building
|
|
primarily with a different system.
|
|
|
|
Given these considerations, GN is an ideal choice for Pigweed's Python build.
|
|
|
|
.. _Configuring setup() using setup.cfg files: https://ipython.readthedocs.io/en/stable/interactive/reference.html#embedding
|
|
.. _Build System Support - How to use it?: https://setuptools.readthedocs.io/en/latest/build_meta.html?highlight=pyproject.toml#how-to-use-it
|