#!/usr/bin/env python3 # # Copyright 2019 - The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import filecmp import logging import os import re import shutil import tempfile from acts.libs.proc import job from acts.libs.proto import proto_utils COMMIT_ID_ENV_KEY = 'PREUPLOAD_COMMIT' GIT_FILE_NAMES_CMD = 'git diff-tree --no-commit-id --name-status -r %s' def proto_generates_gen_file(proto_file, proto_gen_file): """Check that the proto_gen_file matches the compilation result of the proto_file. Params: proto_file: The proto file. proto_gen_file: The generated file. Returns: True if the compiled proto matches the given proto_gen_file. """ with tempfile.TemporaryDirectory() as tmp_dir: module_name = proto_utils.compile_proto(proto_file, tmp_dir) if not module_name: return False tmp_proto_gen_file = os.path.join(tmp_dir, '%s.py' % module_name) return filecmp.cmp(tmp_proto_gen_file, proto_gen_file) def verify_protos_update_generated_files(proto_files, proto_gen_files): """For each proto file in proto_files, find the corresponding generated file in either proto_gen_files, or in the directory tree of the proto. Verify that the generated file matches the compilation result of the proto. Params: proto_files: List of proto files. proto_gen_files: List of generated files. """ success = True gen_files = set(proto_gen_files) for proto_file in proto_files: gen_filename = os.path.basename(proto_file).replace('.proto', '_pb2.py') gen_file = '' # search the gen_files set first for path in gen_files: if (os.path.basename(path) == gen_filename and path.startswith(os.path.dirname(proto_file))): gen_file = path gen_files.remove(path) break else: # search the proto file's directory for root, _, filenames in os.walk(os.path.dirname(proto_file)): for filename in filenames: if filename == gen_filename: gen_file = os.path.join(root, filename) break if gen_file: break # check that the generated file matches the compiled proto if gen_file and not proto_generates_gen_file(proto_file, gen_file): logging.error('Proto file %s does not compile to %s' % (proto_file, gen_file)) protoc = shutil.which('protoc') if protoc: protoc_command = ' '.join([ protoc, '-I=%s' % os.path.dirname(proto_file), '--python_out=%s' % os.path.dirname(gen_file), proto_file]) logging.error('Run this command to re-generate file from proto' '\n%s' % protoc_command) success = False return success def main(): if COMMIT_ID_ENV_KEY not in os.environ: logging.error('Missing commit id in environment.') exit(1) # get list of *.proto and *_pb2.py files from commit, then compare proto_files = [] proto_gen_files = [] git_cmd = GIT_FILE_NAMES_CMD % os.environ[COMMIT_ID_ENV_KEY] lines = job.run(git_cmd).stdout.splitlines() for line in lines: match = re.match(r'(\S+)\s+(.*)', line) status, f = match.group(1), match.group(2) if status != 'D': if f.endswith('.proto'): proto_files.append(os.path.abspath(f)) if f.endswith('_pb2.py'): proto_gen_files.append(os.path.abspath(f)) exit(not verify_protos_update_generated_files(proto_files, proto_gen_files)) if __name__ == '__main__': main()