1#!/usr/bin/env python
2
3# Copyright 2016 gRPC authors.
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
17from __future__ import print_function
18
19import errno
20import filecmp
21import glob
22import os
23import os.path
24import shutil
25import subprocess
26import sys
27import traceback
28import uuid
29
30DEPS_FILE_CONTENT = """
31# Copyright 2017 gRPC authors.
32#
33# Licensed under the Apache License, Version 2.0 (the "License");
34# you may not use this file except in compliance with the License.
35# You may obtain a copy of the License at
36#
37#     http://www.apache.org/licenses/LICENSE-2.0
38#
39# Unless required by applicable law or agreed to in writing, software
40# distributed under the License is distributed on an "AS IS" BASIS,
41# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
42# See the License for the specific language governing permissions and
43# limitations under the License.
44
45# AUTO-GENERATED BY make_grpcio_tools.py!
46CC_FILES={cc_files}
47PROTO_FILES={proto_files}
48
49CC_INCLUDE={cc_include}
50PROTO_INCLUDE={proto_include}
51
52{commit_hash}
53"""
54
55COMMIT_HASH_PREFIX = 'PROTOBUF_SUBMODULE_VERSION="'
56COMMIT_HASH_SUFFIX = '"'
57
58# Bazel query result prefix for expected source files in protobuf.
59PROTOBUF_CC_PREFIX = '//:src/'
60PROTOBUF_PROTO_PREFIX = '//:src/'
61
62GRPC_ROOT = os.path.abspath(
63    os.path.join(os.path.dirname(os.path.abspath(__file__)), '..', '..', '..'))
64
65GRPC_PYTHON_ROOT = os.path.join(GRPC_ROOT, 'tools', 'distrib', 'python',
66                                'grpcio_tools')
67
68GRPC_PYTHON_PROTOBUF_RELATIVE_ROOT = os.path.join('third_party', 'protobuf',
69                                                  'src')
70GRPC_PROTOBUF = os.path.join(GRPC_ROOT, GRPC_PYTHON_PROTOBUF_RELATIVE_ROOT)
71GRPC_PROTOBUF_SUBMODULE_ROOT = os.path.join(GRPC_ROOT, 'third_party',
72                                            'protobuf')
73GRPC_PROTOC_PLUGINS = os.path.join(GRPC_ROOT, 'src', 'compiler')
74GRPC_PYTHON_PROTOBUF = os.path.join(GRPC_PYTHON_ROOT, 'third_party', 'protobuf',
75                                    'src')
76GRPC_PYTHON_PROTOC_PLUGINS = os.path.join(GRPC_PYTHON_ROOT, 'grpc_root', 'src',
77                                          'compiler')
78GRPC_PYTHON_PROTOC_LIB_DEPS = os.path.join(GRPC_PYTHON_ROOT,
79                                           'protoc_lib_deps.py')
80
81GRPC_INCLUDE = os.path.join(GRPC_ROOT, 'include')
82GRPC_PYTHON_INCLUDE = os.path.join(GRPC_PYTHON_ROOT, 'grpc_root', 'include')
83
84BAZEL_DEPS = os.path.join(GRPC_ROOT, 'tools', 'distrib', 'python',
85                          'bazel_deps.sh')
86BAZEL_DEPS_PROTOC_LIB_QUERY = '//:protoc_lib'
87BAZEL_DEPS_COMMON_PROTOS_QUERY = '//:well_known_protos'
88
89
90def protobuf_submodule_commit_hash():
91    """Gets the commit hash for the HEAD of the protobuf submodule currently
92     checked out."""
93    cwd = os.getcwd()
94    os.chdir(GRPC_PROTOBUF_SUBMODULE_ROOT)
95    output = subprocess.check_output(['git', 'rev-parse', 'HEAD'])
96    os.chdir(cwd)
97    return output.splitlines()[0].strip()
98
99
100def bazel_query(query):
101    print('Running "bazel query %s"' % query)
102    output = subprocess.check_output([BAZEL_DEPS, query])
103    return output.splitlines()
104
105
106def get_deps():
107    """Write the result of the bazel query `query` against protobuf to
108     `out_file`."""
109    cc_files_output = bazel_query(BAZEL_DEPS_PROTOC_LIB_QUERY)
110    cc_files = [
111        name[len(PROTOBUF_CC_PREFIX):]
112        for name in cc_files_output
113        if name.endswith('.cc') and name.startswith(PROTOBUF_CC_PREFIX)
114    ]
115    proto_files_output = bazel_query(BAZEL_DEPS_COMMON_PROTOS_QUERY)
116    proto_files = [
117        name[len(PROTOBUF_PROTO_PREFIX):]
118        for name in proto_files_output
119        if name.endswith('.proto') and name.startswith(PROTOBUF_PROTO_PREFIX)
120    ]
121    commit_hash = protobuf_submodule_commit_hash()
122    deps_file_content = DEPS_FILE_CONTENT.format(
123        cc_files=cc_files,
124        proto_files=proto_files,
125        cc_include=repr(GRPC_PYTHON_PROTOBUF_RELATIVE_ROOT),
126        proto_include=repr(GRPC_PYTHON_PROTOBUF_RELATIVE_ROOT),
127        commit_hash=COMMIT_HASH_PREFIX + commit_hash + COMMIT_HASH_SUFFIX)
128    return deps_file_content
129
130
131def long_path(path):
132    if os.name == 'nt':
133        return '\\\\?\\' + path
134    else:
135        return path
136
137
138def main():
139    os.chdir(GRPC_ROOT)
140
141    for source, target in [(GRPC_PROTOBUF, GRPC_PYTHON_PROTOBUF),
142                           (GRPC_PROTOC_PLUGINS, GRPC_PYTHON_PROTOC_PLUGINS),
143                           (GRPC_INCLUDE, GRPC_PYTHON_INCLUDE)]:
144        for source_dir, _, files in os.walk(source):
145            target_dir = os.path.abspath(
146                os.path.join(target, os.path.relpath(source_dir, source)))
147            try:
148                os.makedirs(target_dir)
149            except OSError as error:
150                if error.errno != errno.EEXIST:
151                    raise
152            for relative_file in files:
153                source_file = os.path.abspath(
154                    os.path.join(source_dir, relative_file))
155                target_file = os.path.abspath(
156                    os.path.join(target_dir, relative_file))
157                shutil.copyfile(source_file, target_file)
158
159    try:
160        print('Invoking "bazel query" to gather the protobuf dependencies.')
161        protoc_lib_deps_content = get_deps()
162    except Exception as error:
163        # We allow this script to succeed even if we couldn't get the dependencies,
164        # as then we can assume that even without a successful bazel run the
165        # dependencies currently in source control are 'good enough'.
166        sys.stderr.write("Got non-fatal error:\n")
167        traceback.print_exc(file=sys.stderr)
168        return
169    # If we successfully got the dependencies, truncate and rewrite the deps file.
170    with open(GRPC_PYTHON_PROTOC_LIB_DEPS, 'w') as deps_file:
171        deps_file.write(protoc_lib_deps_content)
172    print('File "%s" updated.' % GRPC_PYTHON_PROTOC_LIB_DEPS)
173    print('Done.')
174
175
176if __name__ == '__main__':
177    main()
178