lin
2025-08-14 dae8bad597b6607a449b32bf76c523423f7720ed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
#!/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 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-only -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
 
    # fail if there are unpaired gen_files
    if gen_files:
        logging.error('Generated *_pb2.py files modified without updating '
                      'source .proto files: \n\t%s' % '\n\t'.join(gen_files))
        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]
    files = job.run(git_cmd).stdout.splitlines()
    for f in files:
        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()