Fix for x86_64 build fail
[platform/upstream/connectedhomeip.git] / third_party / pigweed / repo / pw_presubmit / py / pw_presubmit / python_checks.py
1 # Copyright 2020 The Pigweed Authors
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 # use this file except in compliance with the License. You may obtain a copy of
5 # the License at
6 #
7 #     https://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 # License for the specific language governing permissions and limitations under
13 # the License.
14 """Preconfigured checks for Python code.
15
16 These checks assume that they are running in a preconfigured Python environment.
17 """
18
19 import logging
20 import os
21 from pathlib import Path
22 import sys
23 from typing import Callable, Iterable, List, Set, Tuple
24
25 try:
26     import pw_presubmit
27 except ImportError:
28     # Append the pw_presubmit package path to the module search path to allow
29     # running this module without installing the pw_presubmit package.
30     sys.path.append(os.path.dirname(os.path.dirname(
31         os.path.abspath(__file__))))
32     import pw_presubmit
33
34 from pw_presubmit import call, filter_paths
35 from pw_presubmit.git_repo import python_packages_containing, list_files
36 from pw_presubmit.git_repo import PythonPackage
37
38 _LOG = logging.getLogger(__name__)
39
40
41 def run_module(*args, **kwargs):
42     return call('python', '-m', *args, **kwargs)
43
44
45 TEST_PATTERNS = ('*_test.py', )
46
47
48 @filter_paths(endswith='.py')
49 def test_python_packages(ctx: pw_presubmit.PresubmitContext,
50                          patterns: Iterable[str] = TEST_PATTERNS) -> None:
51     """Finds and runs test files in Python package directories.
52
53     Finds the Python packages containing the affected paths, then searches
54     within that package for test files. All files matching the provided patterns
55     are executed with Python.
56     """
57     packages: List[PythonPackage] = []
58     for repo in ctx.repos:
59         packages += python_packages_containing(ctx.paths, repo=repo)[0]
60
61     if not packages:
62         _LOG.info('No Python packages were found.')
63         return
64
65     for package in packages:
66         for test in list_files(pathspecs=tuple(patterns),
67                                repo_path=package.root):
68             call('python', test)
69
70
71 @filter_paths(endswith='.py')
72 def pylint(ctx: pw_presubmit.PresubmitContext) -> None:
73     disable_checkers = [
74         # BUG(pwbug/22): Hanging indent check conflicts with YAPF 0.29. For
75         # now, use YAPF's version even if Pylint is doing the correct thing
76         # just to keep operations simpler. When YAPF upstream fixes the issue,
77         # delete this code.
78         #
79         # See also: https://github.com/google/yapf/issues/781
80         'bad-continuation',
81     ]
82     run_module(
83         'pylint',
84         '--jobs=0',
85         f'--disable={",".join(disable_checkers)}',
86         *ctx.paths,
87         cwd=ctx.root,
88     )
89
90
91 @filter_paths(endswith='.py')
92 def mypy(ctx: pw_presubmit.PresubmitContext) -> None:
93     """Runs mypy on all paths and their packages."""
94     packages: List[PythonPackage] = []
95     other_files: List[Path] = []
96
97     for repo, paths in ctx.paths_by_repo().items():
98         new_packages, files = python_packages_containing(paths, repo=repo)
99         packages += new_packages
100         other_files += files
101
102         for package in new_packages:
103             other_files += package.other_files
104
105     # Under some circumstances, mypy cannot check multiple Python files with the
106     # same module name. Group filenames so that no duplicates occur in the same
107     # mypy invocation. Also, omit setup.py from mypy checks.
108     filename_sets: List[Set[str]] = [set()]
109     path_sets: List[List[Path]] = [list(p.package for p in packages)]
110
111     for path in (p for p in other_files if p.name != 'setup.py'):
112         for filenames, paths in zip(filename_sets, path_sets):
113             if path.name not in filenames:
114                 paths.append(path)
115                 filenames.add(path.name)
116                 break
117         else:
118             path_sets.append([path])
119             filename_sets.append({path.name})
120
121     env = os.environ.copy()
122     # Use this environment variable to force mypy to colorize output.
123     # See https://github.com/python/mypy/issues/7771
124     env['MYPY_FORCE_COLOR'] = '1'
125
126     for paths in path_sets:
127         run_module(
128             'mypy',
129             *paths,
130             '--pretty',
131             '--color-output',
132             '--show-error-codes',
133             # TODO(pwbug/146): Some imports from installed packages fail. These
134             # imports should be fixed and this option removed. See
135             # https://mypy.readthedocs.io/en/stable/installed_packages.html
136             '--ignore-missing-imports',
137             env=env)
138
139
140 _ALL_CHECKS = (
141     test_python_packages,
142     pylint,
143     mypy,
144 )
145
146
147 def all_checks(endswith: str = '.py',
148                **filter_paths_args) -> Tuple[Callable, ...]:
149     return tuple(
150         filter_paths(endswith=endswith, **filter_paths_args)(function)
151         for function in _ALL_CHECKS)