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