xref: /aosp_15_r20/tools/asuite/preupload_hook_script.py (revision c2e18aaa1096c836b086f94603d04f4eb9cf37f5)
1*c2e18aaaSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*c2e18aaaSAndroid Build Coastguard Worker# Copyright 2019, The Android Open Source Project
3*c2e18aaaSAndroid Build Coastguard Worker#
4*c2e18aaaSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
5*c2e18aaaSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
6*c2e18aaaSAndroid Build Coastguard Worker# You may obtain a copy of the License at
7*c2e18aaaSAndroid Build Coastguard Worker#
8*c2e18aaaSAndroid Build Coastguard Worker#     http://www.apache.org/licenses/LICENSE-2.0
9*c2e18aaaSAndroid Build Coastguard Worker#
10*c2e18aaaSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
11*c2e18aaaSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
12*c2e18aaaSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13*c2e18aaaSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
14*c2e18aaaSAndroid Build Coastguard Worker# limitations under the License.
15*c2e18aaaSAndroid Build Coastguard Worker
16*c2e18aaaSAndroid Build Coastguard Worker"""Run at preupload hook to perform necessary checks and formatting."""
17*c2e18aaaSAndroid Build Coastguard Worker
18*c2e18aaaSAndroid Build Coastguard Workerimport argparse
19*c2e18aaaSAndroid Build Coastguard Workerimport concurrent.futures
20*c2e18aaaSAndroid Build Coastguard Workerimport multiprocessing
21*c2e18aaaSAndroid Build Coastguard Workerimport pathlib
22*c2e18aaaSAndroid Build Coastguard Workerimport shlex
23*c2e18aaaSAndroid Build Coastguard Workerimport subprocess
24*c2e18aaaSAndroid Build Coastguard Workerimport sys
25*c2e18aaaSAndroid Build Coastguard Worker
26*c2e18aaaSAndroid Build Coastguard WorkerASUITE_HOME = pathlib.Path(__file__).resolve().parent
27*c2e18aaaSAndroid Build Coastguard Worker
28*c2e18aaaSAndroid Build Coastguard Worker
29*c2e18aaaSAndroid Build Coastguard Workerdef _filter_python_files(files: list[pathlib.Path]) -> list[pathlib.Path]:
30*c2e18aaaSAndroid Build Coastguard Worker  """Filter a list of files and return a new list of python files only."""
31*c2e18aaaSAndroid Build Coastguard Worker  return [file for file in files if file.suffix == '.py']
32*c2e18aaaSAndroid Build Coastguard Worker
33*c2e18aaaSAndroid Build Coastguard Worker
34*c2e18aaaSAndroid Build Coastguard Workerdef _check_run_shell_command(cmd: str, cwd: str = None) -> None:
35*c2e18aaaSAndroid Build Coastguard Worker  """Run a shell command and raise error if failed."""
36*c2e18aaaSAndroid Build Coastguard Worker  if subprocess.run(shlex.split(cmd), cwd=cwd, check=False).returncode:
37*c2e18aaaSAndroid Build Coastguard Worker    print('Preupload files did not pass Asuite preupload hook script.')
38*c2e18aaaSAndroid Build Coastguard Worker    sys.exit(1)
39*c2e18aaaSAndroid Build Coastguard Worker
40*c2e18aaaSAndroid Build Coastguard Worker
41*c2e18aaaSAndroid Build Coastguard Workerdef _run_python_lint(lint_bin: str, files: list[pathlib.Path]) -> None:
42*c2e18aaaSAndroid Build Coastguard Worker  """Run python lint binary on python files."""
43*c2e18aaaSAndroid Build Coastguard Worker  run_lint_on_file = lambda file: subprocess.run(
44*c2e18aaaSAndroid Build Coastguard Worker      shlex.split(f'{lint_bin} {file.as_posix()}'),
45*c2e18aaaSAndroid Build Coastguard Worker      check=False,
46*c2e18aaaSAndroid Build Coastguard Worker      capture_output=True,
47*c2e18aaaSAndroid Build Coastguard Worker  )
48*c2e18aaaSAndroid Build Coastguard Worker
49*c2e18aaaSAndroid Build Coastguard Worker  cpu_count = multiprocessing.cpu_count()
50*c2e18aaaSAndroid Build Coastguard Worker  with concurrent.futures.ThreadPoolExecutor(max_workers=cpu_count) as executor:
51*c2e18aaaSAndroid Build Coastguard Worker    completed_processes = executor.map(
52*c2e18aaaSAndroid Build Coastguard Worker        run_lint_on_file, _filter_python_files(files)
53*c2e18aaaSAndroid Build Coastguard Worker    )
54*c2e18aaaSAndroid Build Coastguard Worker
55*c2e18aaaSAndroid Build Coastguard Worker  has_format_issue = False
56*c2e18aaaSAndroid Build Coastguard Worker  for process in completed_processes:
57*c2e18aaaSAndroid Build Coastguard Worker    if not process.returncode:
58*c2e18aaaSAndroid Build Coastguard Worker      continue
59*c2e18aaaSAndroid Build Coastguard Worker    print(process.stdout.decode())
60*c2e18aaaSAndroid Build Coastguard Worker    has_format_issue = True
61*c2e18aaaSAndroid Build Coastguard Worker
62*c2e18aaaSAndroid Build Coastguard Worker  if has_format_issue:
63*c2e18aaaSAndroid Build Coastguard Worker    sys.exit(1)
64*c2e18aaaSAndroid Build Coastguard Worker
65*c2e18aaaSAndroid Build Coastguard Worker
66*c2e18aaaSAndroid Build Coastguard Workerdef _run_pylint(files: list[pathlib.Path]) -> None:
67*c2e18aaaSAndroid Build Coastguard Worker  """Run pylint on python files."""
68*c2e18aaaSAndroid Build Coastguard Worker  _run_python_lint('pylint', files)
69*c2e18aaaSAndroid Build Coastguard Worker
70*c2e18aaaSAndroid Build Coastguard Worker
71*c2e18aaaSAndroid Build Coastguard Workerdef _run_gpylint(files: list[pathlib.Path]) -> None:
72*c2e18aaaSAndroid Build Coastguard Worker  """Run gpylint on python files if gpylint is available."""
73*c2e18aaaSAndroid Build Coastguard Worker  if subprocess.run(
74*c2e18aaaSAndroid Build Coastguard Worker      shlex.split('which gpylint'),
75*c2e18aaaSAndroid Build Coastguard Worker      check=False,
76*c2e18aaaSAndroid Build Coastguard Worker  ).returncode:
77*c2e18aaaSAndroid Build Coastguard Worker    print('gpylint not available. Will use pylint instead.')
78*c2e18aaaSAndroid Build Coastguard Worker    _run_pylint(files)
79*c2e18aaaSAndroid Build Coastguard Worker    return
80*c2e18aaaSAndroid Build Coastguard Worker
81*c2e18aaaSAndroid Build Coastguard Worker  _run_python_lint('gpylint', files)
82*c2e18aaaSAndroid Build Coastguard Worker
83*c2e18aaaSAndroid Build Coastguard Worker
84*c2e18aaaSAndroid Build Coastguard Workerdef _run_pyformat(files: list[pathlib.Path]) -> None:
85*c2e18aaaSAndroid Build Coastguard Worker  """Run pyformat on certain projects."""
86*c2e18aaaSAndroid Build Coastguard Worker  if subprocess.run(
87*c2e18aaaSAndroid Build Coastguard Worker      shlex.split('which pyformat'),
88*c2e18aaaSAndroid Build Coastguard Worker      check=False,
89*c2e18aaaSAndroid Build Coastguard Worker  ).returncode:
90*c2e18aaaSAndroid Build Coastguard Worker    print('pyformat not available. Will skip auto formatting.')
91*c2e18aaaSAndroid Build Coastguard Worker    return
92*c2e18aaaSAndroid Build Coastguard Worker
93*c2e18aaaSAndroid Build Coastguard Worker  def _run_pyformat_on_file(file):
94*c2e18aaaSAndroid Build Coastguard Worker    completed_process = subprocess.run(
95*c2e18aaaSAndroid Build Coastguard Worker        shlex.split('pyformat --force_quote_type single ' + file.as_posix()),
96*c2e18aaaSAndroid Build Coastguard Worker        capture_output=True,
97*c2e18aaaSAndroid Build Coastguard Worker        check=False,
98*c2e18aaaSAndroid Build Coastguard Worker    )
99*c2e18aaaSAndroid Build Coastguard Worker
100*c2e18aaaSAndroid Build Coastguard Worker    if completed_process.stdout:
101*c2e18aaaSAndroid Build Coastguard Worker      subprocess.run(
102*c2e18aaaSAndroid Build Coastguard Worker          shlex.split(
103*c2e18aaaSAndroid Build Coastguard Worker              'pyformat -i --force_quote_type single ' + file.as_posix()
104*c2e18aaaSAndroid Build Coastguard Worker          ),
105*c2e18aaaSAndroid Build Coastguard Worker          check=False,
106*c2e18aaaSAndroid Build Coastguard Worker      )
107*c2e18aaaSAndroid Build Coastguard Worker      return True
108*c2e18aaaSAndroid Build Coastguard Worker    return False
109*c2e18aaaSAndroid Build Coastguard Worker
110*c2e18aaaSAndroid Build Coastguard Worker  cpu_count = multiprocessing.cpu_count()
111*c2e18aaaSAndroid Build Coastguard Worker  with concurrent.futures.ThreadPoolExecutor(max_workers=cpu_count) as executor:
112*c2e18aaaSAndroid Build Coastguard Worker    need_reformat = executor.map(
113*c2e18aaaSAndroid Build Coastguard Worker        _run_pyformat_on_file, _filter_python_files(files)
114*c2e18aaaSAndroid Build Coastguard Worker    )
115*c2e18aaaSAndroid Build Coastguard Worker
116*c2e18aaaSAndroid Build Coastguard Worker  if any(need_reformat):
117*c2e18aaaSAndroid Build Coastguard Worker    print(
118*c2e18aaaSAndroid Build Coastguard Worker        'Reformatting completed. Please add the modified files to git and rerun'
119*c2e18aaaSAndroid Build Coastguard Worker        ' the repo preupload hook.'
120*c2e18aaaSAndroid Build Coastguard Worker    )
121*c2e18aaaSAndroid Build Coastguard Worker    sys.exit(1)
122*c2e18aaaSAndroid Build Coastguard Worker
123*c2e18aaaSAndroid Build Coastguard Worker
124*c2e18aaaSAndroid Build Coastguard Workerdef _run_legacy_unittests() -> None:
125*c2e18aaaSAndroid Build Coastguard Worker  """Run unittests for asuite_plugin."""
126*c2e18aaaSAndroid Build Coastguard Worker  asuite_plugin_path = ASUITE_HOME.joinpath('asuite_plugin').as_posix()
127*c2e18aaaSAndroid Build Coastguard Worker  _check_run_shell_command(
128*c2e18aaaSAndroid Build Coastguard Worker      f'{asuite_plugin_path}/gradlew test', asuite_plugin_path
129*c2e18aaaSAndroid Build Coastguard Worker  )
130*c2e18aaaSAndroid Build Coastguard Worker
131*c2e18aaaSAndroid Build Coastguard Worker
132*c2e18aaaSAndroid Build Coastguard Workerdef _filter_files_for_projects(
133*c2e18aaaSAndroid Build Coastguard Worker    files: list[pathlib.Path], projects: list[str], root_files: bool
134*c2e18aaaSAndroid Build Coastguard Worker) -> tuple[list[pathlib.Path], list[pathlib.Path]]:
135*c2e18aaaSAndroid Build Coastguard Worker  """Filter a list of files according to project names.
136*c2e18aaaSAndroid Build Coastguard Worker
137*c2e18aaaSAndroid Build Coastguard Worker  Args:
138*c2e18aaaSAndroid Build Coastguard Worker      files: list of files to filter.
139*c2e18aaaSAndroid Build Coastguard Worker      projects: list of project names to match, e.g. ['atest'].
140*c2e18aaaSAndroid Build Coastguard Worker      root_files: whether to treat files under the asuite root directory as
141*c2e18aaaSAndroid Build Coastguard Worker        matched files.
142*c2e18aaaSAndroid Build Coastguard Worker
143*c2e18aaaSAndroid Build Coastguard Worker  Returns:
144*c2e18aaaSAndroid Build Coastguard Worker      A tuple of a list of files matching the projects and a list of files not
145*c2e18aaaSAndroid Build Coastguard Worker      matching the projects.
146*c2e18aaaSAndroid Build Coastguard Worker  """
147*c2e18aaaSAndroid Build Coastguard Worker  matched_files = []
148*c2e18aaaSAndroid Build Coastguard Worker  not_matched_files = []
149*c2e18aaaSAndroid Build Coastguard Worker  project_paths = [
150*c2e18aaaSAndroid Build Coastguard Worker      ASUITE_HOME.joinpath(project).resolve().as_posix() for project in projects
151*c2e18aaaSAndroid Build Coastguard Worker  ]
152*c2e18aaaSAndroid Build Coastguard Worker  for file in files:
153*c2e18aaaSAndroid Build Coastguard Worker    if file.as_posix().startswith(tuple(project_paths)):
154*c2e18aaaSAndroid Build Coastguard Worker      matched_files.append(file)
155*c2e18aaaSAndroid Build Coastguard Worker    elif root_files and file.parent == ASUITE_HOME:
156*c2e18aaaSAndroid Build Coastguard Worker      matched_files.append(file)
157*c2e18aaaSAndroid Build Coastguard Worker    else:
158*c2e18aaaSAndroid Build Coastguard Worker      not_matched_files.append(file)
159*c2e18aaaSAndroid Build Coastguard Worker
160*c2e18aaaSAndroid Build Coastguard Worker  return matched_files, not_matched_files
161*c2e18aaaSAndroid Build Coastguard Worker
162*c2e18aaaSAndroid Build Coastguard Worker
163*c2e18aaaSAndroid Build Coastguard Workerdef get_preupload_files() -> list[pathlib.Path]:
164*c2e18aaaSAndroid Build Coastguard Worker  """Get the list of files to be uploaded."""
165*c2e18aaaSAndroid Build Coastguard Worker  parser = argparse.ArgumentParser()
166*c2e18aaaSAndroid Build Coastguard Worker  parser.add_argument('preupload_files', nargs='*', help='Files to upload.')
167*c2e18aaaSAndroid Build Coastguard Worker  args = parser.parse_args()
168*c2e18aaaSAndroid Build Coastguard Worker  files_to_upload = args.preupload_files
169*c2e18aaaSAndroid Build Coastguard Worker  if not files_to_upload:
170*c2e18aaaSAndroid Build Coastguard Worker    # When running by users directly, only consider:
171*c2e18aaaSAndroid Build Coastguard Worker    # added(A), renamed(R) and modified(M) files
172*c2e18aaaSAndroid Build Coastguard Worker    # and store them in files_to_upload.
173*c2e18aaaSAndroid Build Coastguard Worker    cmd = "git status --short | egrep [ARM] | awk '{print $NF}'"
174*c2e18aaaSAndroid Build Coastguard Worker    files_to_upload = subprocess.check_output(
175*c2e18aaaSAndroid Build Coastguard Worker        cmd, shell=True, encoding='utf-8'
176*c2e18aaaSAndroid Build Coastguard Worker    ).splitlines()
177*c2e18aaaSAndroid Build Coastguard Worker    if files_to_upload:
178*c2e18aaaSAndroid Build Coastguard Worker      print('Modified files: %s' % files_to_upload)
179*c2e18aaaSAndroid Build Coastguard Worker  file_paths_to_upload = [
180*c2e18aaaSAndroid Build Coastguard Worker      pathlib.Path(file).resolve() for file in files_to_upload
181*c2e18aaaSAndroid Build Coastguard Worker  ]
182*c2e18aaaSAndroid Build Coastguard Worker  return [file for file in file_paths_to_upload if file.exists()]
183*c2e18aaaSAndroid Build Coastguard Worker
184*c2e18aaaSAndroid Build Coastguard Worker
185*c2e18aaaSAndroid Build Coastguard Workerif __name__ == '__main__':
186*c2e18aaaSAndroid Build Coastguard Worker  preupload_files = get_preupload_files()
187*c2e18aaaSAndroid Build Coastguard Worker
188*c2e18aaaSAndroid Build Coastguard Worker  gpylint_project_files, other_files = _filter_files_for_projects(
189*c2e18aaaSAndroid Build Coastguard Worker      preupload_files, ['atest', 'experiments/a'], root_files=True
190*c2e18aaaSAndroid Build Coastguard Worker  )
191*c2e18aaaSAndroid Build Coastguard Worker  _run_pylint(other_files)
192*c2e18aaaSAndroid Build Coastguard Worker  _run_pyformat(gpylint_project_files)
193*c2e18aaaSAndroid Build Coastguard Worker  _run_gpylint(gpylint_project_files)
194*c2e18aaaSAndroid Build Coastguard Worker
195*c2e18aaaSAndroid Build Coastguard Worker  asuite_plugin_files, _ = _filter_files_for_projects(
196*c2e18aaaSAndroid Build Coastguard Worker      preupload_files, ['asuite_plugin'], root_files=False
197*c2e18aaaSAndroid Build Coastguard Worker  )
198*c2e18aaaSAndroid Build Coastguard Worker  if asuite_plugin_files:
199*c2e18aaaSAndroid Build Coastguard Worker    _run_legacy_unittests()
200