1 .. _module-pw_presubmit:
6 The presubmit module provides Python tools for running presubmit checks and
7 checking and fixing code format. It also includes the presubmit check script for
8 the Pigweed repository, ``pigweed_presubmit.py``.
10 Presubmit checks are essential tools, but they take work to set up, and
11 projects don’t always get around to it. The ``pw_presubmit`` module provides
12 tools for setting up high quality presubmit checks for any project. We use this
13 framework to run Pigweed’s presubmit on our workstations and in our automated
16 The ``pw_presubmit`` module also includes ``pw format``, a tool that provides a
17 unified interface for automatically formatting code in a variety of languages.
18 With ``pw format``, you can format C, C++, Python, GN, and Go code according to
19 configurations defined by your project. ``pw format`` leverages existing tools
20 like ``clang-format``, and it’s simple to add support for new languages.
22 .. image:: docs/pw_presubmit_demo.gif
23 :alt: ``pw format`` demo
26 The ``pw_presubmit`` package includes presubmit checks that can be used with any
27 project. These checks include:
29 * Check code format of several languages including C, C++, and Python
30 * Initialize a Python environment
31 * Run all Python tests
34 * Ensure source files are included in the GN and Bazel builds
35 * Build and run all tests with GN
36 * Build and run all tests with Bazel
37 * Ensure all header files contain ``#pragma once``
44 -------------------------------------------
45 Creating a presubmit check for your project
46 -------------------------------------------
47 Creating a presubmit check for a project using ``pw_presubmit`` is simple, but
48 requires some customization. Projects must define their own presubmit check
49 Python script that uses the ``pw_presubmit`` package.
51 A project's presubmit script can be registered as a
52 :ref:`pw_cli <module-pw_cli>` plugin, so that it can be run as ``pw
55 Setting up the command-line interface
56 -------------------------------------
57 The ``pw_presubmit.cli`` module sets up the command-line interface for a
58 presubmit script. This defines a standard set of arguments for invoking
59 presubmit checks. Its use is optional, but recommended.
63 .. automodule:: pw_presubmit.cli
64 :members: add_arguments, run
68 A presubmit check is defined as a function or other callable. The function must
69 accept one argument: a ``PresubmitContext``, which provides the paths on which
70 to run. Presubmit checks communicate failure by raising an exception.
72 Presubmit checks may use the ``filter_paths`` decorator to automatically filter
73 the paths list for file types they care about.
75 Either of these functions could be used as presubmit checks:
77 .. code-block:: python
79 @pw_presubmit.filter_paths(endswith='.py')
80 def file_contains_ni(ctx: PresubmitContext):
81 for path in ctx.paths:
82 with open(path) as file:
83 contents = file.read()
84 if 'ni' not in contents and 'nee' not in contents:
85 raise PresumitFailure('Files must say "ni"!', path=path)
88 subprocess.run(['make', 'release'], check=True)
90 Presubmit checks functions are grouped into "programs" -- a named series of
91 checks. Projects may find it helpful to have programs for different purposes,
92 such as a quick program for local use and a full program for automated use. The
93 :ref:`example script <example-script>` uses ``pw_presubmit.Programs`` to define
94 ``quick`` and ``full`` programs.
98 .. automodule:: pw_presubmit
99 :members: filter_paths, call, PresubmitFailure, Programs
105 A simple example presubmit check script follows. This can be copied-and-pasted
106 to serve as a starting point for a project's presubmit check script.
108 See ``pigweed_presubmit.py`` for a more complex presubmit check script example.
110 .. code-block:: python
112 """Example presubmit check script."""
117 from pathlib import Path
120 from typing import List, Pattern
125 print('ERROR: Activate the environment before running presubmits!',
130 from pw_presubmit import build, cli, environment, format_code, git_repo
131 from pw_presubmit import python_checks, filter_paths, PresubmitContext
132 from pw_presubmit.install_hook import install_hook
134 # Set up variables for key project paths.
135 PROJECT_ROOT = Path(os.environ['MY_PROJECT_ROOT'])
136 PIGWEED_ROOT = PROJECT_ROOT / 'pigweed'
141 def init_cipd(ctx: PresubmitContext):
142 environment.init_cipd(PIGWEED_ROOT, ctx.output_dir)
145 def init_virtualenv(ctx: PresubmitContext):
146 environment.init_virtualenv(PIGWEED_ROOT,
148 setup_py_roots=[PROJECT_ROOT])
151 # Rerun the build if files with these extensions change.
152 _BUILD_EXTENSIONS = frozenset(
153 ['.rst', '.gn', '.gni', *format_code.C_FORMAT.extensions])
159 def release_build(ctx: PresubmitContext):
160 build.gn_gen(PROJECT_ROOT, ctx.output_dir, build_type='release')
161 build.ninja(ctx.output_dir)
164 def host_tests(ctx: PresubmitContext):
165 build.gn_gen(PROJECT_ROOT, ctx.output_dir, run_host_tests='true')
166 build.ninja(ctx.output_dir)
169 # Avoid running some checks on certain paths.
171 re.compile(r'^external/'),
172 re.compile(r'^vendor/'),
176 # Use the upstream pragma_once check, but apply a different set of path
177 # filters with @filter_paths.
178 @filter_paths(endswith='.h', exclude=PATH_EXCLUSIONS)
179 def pragma_once(ctx: PresubmitContext):
180 pw_presubmit.pragma_once(ctx)
184 # Presubmit check programs
187 # Initialize an environment for running presubmit checks.
190 # List some presubmit checks to run
193 # Use the upstream formatting checks, with custom path filters applied.
194 format_code.presubmit_checks(exclude=PATH_EXCLUSIONS),
198 QUICK, # Add all checks from the 'quick' program
200 # Use the upstream Python checks, with custom path filters applied.
201 python_checks.all_checks(exclude=PATH_EXCLUSIONS),
204 PROGRAMS = pw_presubmit.Programs(quick=QUICK, full=FULL)
207 def run(install: bool, **presubmit_args) -> int:
208 """Process the --install argument then invoke pw_presubmit."""
210 # Install the presubmit Git pre-push hook, if requested.
212 install_hook(__file__, 'pre-push', ['--base', 'HEAD~'],
216 return cli.run(root=PROJECT_ROOT, **presubmit_args)
220 """Run the presubmit checks for this repository."""
221 parser = argparse.ArgumentParser(description=__doc__)
222 cli.add_arguments(parser, PROGRAMS, 'quick')
224 # Define an option for installing a Git pre-push hook for this script.
228 help='Install the presubmit as a Git pre-push hook and exit.')
230 return run(**vars(parser.parse_args()))
232 if __name__ == '__main__':
233 pw_cli.log.install(logging.INFO)
236 ---------------------
237 Code formatting tools
238 ---------------------
239 The ``pw_presubmit.format_code`` module formats supported source files using
240 external code format tools. The file ``format_code.py`` can be invoked directly
241 from the command line or from ``pw`` as ``pw format``.