1#!/usr/bin/env python3 2# 3# Copyright 2019 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17import filecmp 18import logging 19import os 20import re 21import shutil 22import tempfile 23 24from acts.libs.proc import job 25from acts.libs.proto import proto_utils 26 27COMMIT_ID_ENV_KEY = 'PREUPLOAD_COMMIT' 28GIT_FILE_NAMES_CMD = 'git diff-tree --no-commit-id --name-status -r %s' 29 30 31def proto_generates_gen_file(proto_file, proto_gen_file): 32 """Check that the proto_gen_file matches the compilation result of the 33 proto_file. 34 35 Params: 36 proto_file: The proto file. 37 proto_gen_file: The generated file. 38 39 Returns: True if the compiled proto matches the given proto_gen_file. 40 """ 41 with tempfile.TemporaryDirectory() as tmp_dir: 42 module_name = proto_utils.compile_proto(proto_file, tmp_dir) 43 if not module_name: 44 return False 45 tmp_proto_gen_file = os.path.join(tmp_dir, '%s.py' % module_name) 46 return filecmp.cmp(tmp_proto_gen_file, proto_gen_file) 47 48 49def verify_protos_update_generated_files(proto_files, proto_gen_files): 50 """For each proto file in proto_files, find the corresponding generated 51 file in either proto_gen_files, or in the directory tree of the proto. 52 Verify that the generated file matches the compilation result of the proto. 53 54 Params: 55 proto_files: List of proto files. 56 proto_gen_files: List of generated files. 57 """ 58 success = True 59 gen_files = set(proto_gen_files) 60 for proto_file in proto_files: 61 gen_filename = os.path.basename(proto_file).replace('.proto', '_pb2.py') 62 gen_file = '' 63 # search the gen_files set first 64 for path in gen_files: 65 if (os.path.basename(path) == gen_filename 66 and path.startswith(os.path.dirname(proto_file))): 67 gen_file = path 68 gen_files.remove(path) 69 break 70 else: 71 # search the proto file's directory 72 for root, _, filenames in os.walk(os.path.dirname(proto_file)): 73 for filename in filenames: 74 if filename == gen_filename: 75 gen_file = os.path.join(root, filename) 76 break 77 if gen_file: 78 break 79 80 # check that the generated file matches the compiled proto 81 if gen_file and not proto_generates_gen_file(proto_file, gen_file): 82 logging.error('Proto file %s does not compile to %s' 83 % (proto_file, gen_file)) 84 protoc = shutil.which('protoc') 85 if protoc: 86 protoc_command = ' '.join([ 87 protoc, '-I=%s' % os.path.dirname(proto_file), 88 '--python_out=%s' % os.path.dirname(gen_file), proto_file]) 89 logging.error('Run this command to re-generate file from proto' 90 '\n%s' % protoc_command) 91 success = False 92 93 return success 94 95 96def main(): 97 if COMMIT_ID_ENV_KEY not in os.environ: 98 logging.error('Missing commit id in environment.') 99 exit(1) 100 101 # get list of *.proto and *_pb2.py files from commit, then compare 102 proto_files = [] 103 proto_gen_files = [] 104 git_cmd = GIT_FILE_NAMES_CMD % os.environ[COMMIT_ID_ENV_KEY] 105 lines = job.run(git_cmd).stdout.splitlines() 106 for line in lines: 107 match = re.match(r'(\S+)\s+(.*)', line) 108 status, f = match.group(1), match.group(2) 109 if status != 'D': 110 if f.endswith('.proto'): 111 proto_files.append(os.path.abspath(f)) 112 if f.endswith('_pb2.py'): 113 proto_gen_files.append(os.path.abspath(f)) 114 exit(not verify_protos_update_generated_files(proto_files, proto_gen_files)) 115 116 117if __name__ == '__main__': 118 main() 119