blob: 427f15ce48525b78738f5a915efd5a1b1da7cfb3 [file] [log] [blame]
Wyatt Hepleree3e02f2019-12-05 10:52:31 -08001.. _chapter-pw-presubmit:
2
3.. default-domain:: cpp
4
5.. highlight:: sh
6
Wyatt Hepler62525fd2020-06-09 09:23:49 -07007============
Wyatt Hepleree3e02f2019-12-05 10:52:31 -08008pw_presubmit
Wyatt Hepler62525fd2020-06-09 09:23:49 -07009============
Wyatt Hepleree3e02f2019-12-05 10:52:31 -080010The presubmit module provides Python tools for running presubmit checks and
11checking and fixing code format. It also includes the presubmit check script for
12the Pigweed repository, ``pigweed_presubmit.py``.
13
Keir Mierle086ef1c2020-03-19 02:03:51 -070014Presubmit checks are essential tools, but they take work to set up, and
15projects dont always get around to it. The ``pw_presubmit`` module provides
16tools for setting up high quality presubmit checks for any project. We use this
17framework to run Pigweeds presubmit on our workstations and in our automated
18building tools.
19
20The ``pw_presubmit`` module also includes ``pw format``, a tool that provides a
21unified interface for automatically formatting code in a variety of languages.
22With ``pw format``, you can format C, C++, Python, GN, and Go code according to
23configurations defined by your project. ``pw format`` leverages existing tools
24like ``clang-format``, and its simple to add support for new languages.
25
26.. image:: ../docs/images/pw_presubmit_demo.gif
27 :alt: ``pw format`` demo
28 :align: left
29
Wyatt Hepler62525fd2020-06-09 09:23:49 -070030The ``pw_presubmit`` package includes presubmit checks that can be used with any
31project. These checks include:
32
33* Check code format of several languages including C, C++, and Python
34* Initialize a Python environment
35* Run all Python tests
36* Run pylint
37* Run mypy
38* Ensure source files are included in the GN and Bazel builds
39* Build and run all tests with GN
40* Build and run all tests with Bazel
41* Ensure all header files contain ``#pragma once``
42
43-------------
Wyatt Hepleree3e02f2019-12-05 10:52:31 -080044Compatibility
Wyatt Hepler62525fd2020-06-09 09:23:49 -070045-------------
Wyatt Hepleree3e02f2019-12-05 10:52:31 -080046Python 3
47
Wyatt Hepler62525fd2020-06-09 09:23:49 -070048-------------------------------------------
49Creating a presubmit check for your project
50-------------------------------------------
51Creating a presubmit check for a project using ``pw_presubmit`` is simple, but
52requires some customization. Projects must define their own presubmit check
53Python script that uses the ``pw_presubmit`` package.
Wyatt Hepler5a4dc592020-04-29 15:56:01 -070054
Wyatt Hepler62525fd2020-06-09 09:23:49 -070055A project's presubmit script can be registered as a
56:ref:`pw_cli <chapter-pw-cli>` plugin, so that it can be run as ``pw
57presubmit``.
58
59Setting up the command-line interface
60-------------------------------------
61The ``pw_presubmit.cli`` module sets up the command-line interface for a
62presubmit script. This defines a standard set of arguments for invoking
63presubmit checks. Its use is optional, but recommended.
64
65pw_presubmit.cli
66~~~~~~~~~~~~~~~~
67.. automodule:: pw_presubmit.cli
68 :members: add_arguments, run
69
70Presubmit checks
71----------------
Wyatt Hepler5a4dc592020-04-29 15:56:01 -070072A presubmit check is defined as a function or other callable. The function must
73accept one argument: a ``PresubmitContext``, which provides the paths on which
Wyatt Hepler62525fd2020-06-09 09:23:49 -070074to run. Presubmit checks communicate failure by raising an exception.
Wyatt Hepleree3e02f2019-12-05 10:52:31 -080075
Wyatt Hepler62525fd2020-06-09 09:23:49 -070076Presubmit checks may use the ``filter_paths`` decorator to automatically filter
77the paths list for file types they care about.
78
79Either of these functions could be used as presubmit checks:
Wyatt Hepleree3e02f2019-12-05 10:52:31 -080080
81.. code-block:: python
82
83 @pw_presubmit.filter_paths(endswith='.py')
Wyatt Hepler5a4dc592020-04-29 15:56:01 -070084 def file_contains_ni(ctx: PresubmitContext):
85 for path in ctx.paths:
Wyatt Hepleree3e02f2019-12-05 10:52:31 -080086 with open(path) as file:
87 contents = file.read()
88 if 'ni' not in contents and 'nee' not in contents:
89 raise PresumitFailure('Files must say "ni"!', path=path)
90
Wyatt Hepler5a4dc592020-04-29 15:56:01 -070091 def run_the_build(_):
Wyatt Hepleree3e02f2019-12-05 10:52:31 -080092 subprocess.run(['make', 'release'], check=True)
93
Wyatt Hepler62525fd2020-06-09 09:23:49 -070094Presubmit checks functions are grouped into "programs" -- a named series of
95checks. Projects may find it helpful to have programs for different purposes,
96such as a quick program for local use and a full program for automated use. The
97:ref:`example script <example-script>` uses ``pw_presubmit.Programs`` to define
98``quick`` and ``full`` programs.
Wyatt Hepleree3e02f2019-12-05 10:52:31 -080099
Wyatt Hepler62525fd2020-06-09 09:23:49 -0700100pw_presubmit
101~~~~~~~~~~~~
102.. automodule:: pw_presubmit
103 :members: filter_paths, call, PresubmitFailure, Programs
Wyatt Hepleree3e02f2019-12-05 10:52:31 -0800104
Wyatt Hepler62525fd2020-06-09 09:23:49 -0700105.. _example-script:
Wyatt Hepleree3e02f2019-12-05 10:52:31 -0800106
Wyatt Hepler62525fd2020-06-09 09:23:49 -0700107Example
108-------
109A simple example presubmit check script follows. This can be copied-and-pasted
110to serve as a starting point for a project's presubmit check script.
Wyatt Hepleree3e02f2019-12-05 10:52:31 -0800111
Wyatt Hepler62525fd2020-06-09 09:23:49 -0700112See ``pigweed_presubmit.py`` for a more complex presubmit check script example.
Wyatt Hepleree3e02f2019-12-05 10:52:31 -0800113
Wyatt Hepler62525fd2020-06-09 09:23:49 -0700114.. code-block:: python
Wyatt Hepleree3e02f2019-12-05 10:52:31 -0800115
Wyatt Hepler62525fd2020-06-09 09:23:49 -0700116 """Example presubmit check script."""
Wyatt Hepleree3e02f2019-12-05 10:52:31 -0800117
Wyatt Hepler62525fd2020-06-09 09:23:49 -0700118 import argparse
119 import logging
120 import os
121 from pathlib import Path
122 import re
123 import sys
124 from typing import List, Pattern
Wyatt Hepler5a4dc592020-04-29 15:56:01 -0700125
Wyatt Hepler62525fd2020-06-09 09:23:49 -0700126 try:
127 import pw_cli.log
128 except ImportError:
129 print('ERROR: Activate the environment before running presubmits!',
130 file=sys.stderr)
131 sys.exit(2)
Wyatt Hepleree3e02f2019-12-05 10:52:31 -0800132
Wyatt Hepler62525fd2020-06-09 09:23:49 -0700133 import pw_presubmit
134 from pw_presubmit import build, cli, environment, format_code, git_repo
135 from pw_presubmit import python_checks, filter_paths, PresubmitContext
136 from pw_presubmit.install_hook import install_hook
Wyatt Hepleree3e02f2019-12-05 10:52:31 -0800137
Wyatt Hepler62525fd2020-06-09 09:23:49 -0700138 # Set up variables for key project paths.
139 PROJECT_ROOT = Path(os.environ['MY_PROJECT_ROOT'])
140 PIGWEED_ROOT = PROJECT_ROOT / 'pigweed'
141
142 #
143 # Initialization
144 #
145 def init_cipd(ctx: PresubmitContext):
146 environment.init_cipd(PIGWEED_ROOT, ctx.output_dir)
147
148
149 def init_virtualenv(ctx: PresubmitContext):
150 environment.init_virtualenv(PIGWEED_ROOT,
151 ctx.output_dir,
152 setup_py_roots=[PROJECT_ROOT])
153
154
155 # Rerun the build if files with these extensions change.
156 _BUILD_EXTENSIONS = frozenset(
157 ['.rst', '.gn', '.gni', *format_code.C_FORMAT.extensions])
158
159
160 #
161 # Presubmit checks
162 #
163 def release_build(ctx: PresubmitContext):
164 build.gn_gen(PROJECT_ROOT, ctx.output_dir, build_type='release')
165 build.ninja(ctx.output_dir)
166
167
168 def host_tests(ctx: PresubmitContext):
169 build.gn_gen(PROJECT_ROOT, ctx.output_dir, run_host_tests='true')
170 build.ninja(ctx.output_dir)
171
172
173 # Avoid running some checks on certain paths.
174 PATH_EXCLUSIONS = (
175 re.compile(r'^external/'),
176 re.compile(r'^vendor/'),
177 )
178
179
180 # Use the upstream pragma_once check, but apply a different set of path
181 # filters with @filter_paths.
182 @filter_paths(endswith='.h', exclude=PATH_EXCLUSIONS)
183 def pragma_once(ctx: PresubmitContext):
184 pw_presubmit.pragma_once(ctx)
185
186
187 #
188 # Presubmit check programs
189 #
190 QUICK = (
191 # Initialize an environment for running presubmit checks.
192 init_cipd,
193 init_virtualenv,
194 # List some presubmit checks to run
195 pragma_once,
196 host_tests,
197 # Use the upstream formatting checks, with custom path filters applied.
198 format_code.presubmit_checks(exclude=PATH_EXCLUSIONS),
199 )
200
201 FULL = (
202 QUICK, # Add all checks from the 'quick' program
203 release_build,
204 # Use the upstream Python checks, with custom path filters applied.
205 python_checks.all_checks(exclude=PATH_EXCLUSIONS),
206 )
207
208 PROGRAMS = pw_presubmit.Programs(quick=QUICK, full=FULL)
209
210
211 def run(install: bool, **presubmit_args) -> int:
212 """Process the --install argument then invoke pw_presubmit."""
213
214 # Install the presubmit Git pre-push hook, if requested.
215 if install:
216 install_hook(__file__, 'pre-push', ['--base', 'HEAD~'],
217 git_repo.root())
218 return 0
219
220 return cli.run(root=PROJECT_ROOT, **presubmit_args)
221
222
223 def main() -> int:
224 """Run the presubmit checks for this repository."""
225 parser = argparse.ArgumentParser(description=__doc__)
226 cli.add_arguments(parser, PROGRAMS, 'quick')
227
228 # Define an option for installing a Git pre-push hook for this script.
229 parser.add_argument(
230 '--install',
231 action='store_true',
232 help='Install the presubmit as a Git pre-push hook and exit.')
233
234 return run(**vars(parser.parse_args()))
235
236 if __name__ == '__main__':
237 pw_cli.log.install(logging.INFO)
238 sys.exit(main())
239
240---------------------
Wyatt Hepler5a4dc592020-04-29 15:56:01 -0700241Code formatting tools
Wyatt Hepler62525fd2020-06-09 09:23:49 -0700242---------------------
Wyatt Hepler5a4dc592020-04-29 15:56:01 -0700243The ``pw_presubmit.format_code`` module formats supported source files using
244external code format tools. The file ``format_code.py`` can be invoked directly
245from the command line or from ``pw`` as ``pw format``.