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 ast
20import os
21import re
22import subprocess
23import sys
24
25os.chdir(os.path.join(os.path.dirname(sys.argv[0]), '../../..'))
26
27git_hash_pattern = re.compile('[0-9a-f]{40}')
28
29# Parse git hashes from submodules
30git_submodules = subprocess.check_output('git submodule',
31                                         shell=True).strip().split('\n')
32git_submodule_hashes = {
33    re.search(git_hash_pattern, s).group() for s in git_submodules
34}
35
36_BAZEL_SKYLIB_DEP_NAME = 'bazel_skylib'
37_BAZEL_TOOLCHAINS_DEP_NAME = 'bazel_toolchains'
38_TWISTED_TWISTED_DEP_NAME = 'com_github_twisted_twisted'
39_YAML_PYYAML_DEP_NAME = 'com_github_yaml_pyyaml'
40_TWISTED_INCREMENTAL_DEP_NAME = 'com_github_twisted_incremental'
41_ZOPEFOUNDATION_ZOPE_INTERFACE_DEP_NAME = 'com_github_zopefoundation_zope_interface'
42_TWISTED_CONSTANTLY_DEP_NAME = 'com_github_twisted_constantly'
43
44_GRPC_DEP_NAMES = [
45    'upb',
46    'boringssl',
47    'zlib',
48    'com_google_protobuf',
49    'com_github_google_googletest',
50    'rules_cc',
51    'com_github_gflags_gflags',
52    'com_github_google_benchmark',
53    'com_github_cares_cares',
54    'com_google_absl',
55    'io_opencensus_cpp',
56    'envoy_api',
57    _BAZEL_SKYLIB_DEP_NAME,
58    _BAZEL_TOOLCHAINS_DEP_NAME,
59    _TWISTED_TWISTED_DEP_NAME,
60    _YAML_PYYAML_DEP_NAME,
61    _TWISTED_INCREMENTAL_DEP_NAME,
62    _ZOPEFOUNDATION_ZOPE_INTERFACE_DEP_NAME,
63    _TWISTED_CONSTANTLY_DEP_NAME,
64    'io_bazel_rules_go',
65    'build_bazel_rules_apple',
66    'build_bazel_apple_support',
67    'libuv',
68]
69
70_GRPC_BAZEL_ONLY_DEPS = [
71    'upb',  # third_party/upb is checked in locally
72    'rules_cc',
73    'com_google_absl',
74    'io_opencensus_cpp',
75    _BAZEL_SKYLIB_DEP_NAME,
76    _BAZEL_TOOLCHAINS_DEP_NAME,
77    _TWISTED_TWISTED_DEP_NAME,
78    _YAML_PYYAML_DEP_NAME,
79    _TWISTED_INCREMENTAL_DEP_NAME,
80    _ZOPEFOUNDATION_ZOPE_INTERFACE_DEP_NAME,
81    _TWISTED_CONSTANTLY_DEP_NAME,
82    'io_bazel_rules_go',
83    'build_bazel_rules_apple',
84    'build_bazel_apple_support',
85]
86
87
88class BazelEvalState(object):
89
90    def __init__(self, names_and_urls, overridden_name=None):
91        self.names_and_urls = names_and_urls
92        self.overridden_name = overridden_name
93
94    def http_archive(self, **args):
95        self.archive(**args)
96
97    def new_http_archive(self, **args):
98        self.archive(**args)
99
100    def bind(self, **args):
101        pass
102
103    def existing_rules(self):
104        if self.overridden_name:
105            return [self.overridden_name]
106        return []
107
108    def archive(self, **args):
109        assert self.names_and_urls.get(args['name']) is None
110        if args['name'] in _GRPC_BAZEL_ONLY_DEPS:
111            self.names_and_urls[args['name']] = 'dont care'
112            return
113        url = args.get('url', None)
114        if not url:
115            # we will only be looking for git commit hashes, so concatenating
116            # the urls is fine.
117            url = ' '.join(args['urls'])
118        self.names_and_urls[args['name']] = url
119
120    def git_repository(self, **args):
121        assert self.names_and_urls.get(args['name']) is None
122        if args['name'] in _GRPC_BAZEL_ONLY_DEPS:
123            self.names_and_urls[args['name']] = 'dont care'
124            return
125        self.names_and_urls[args['name']] = args['remote']
126
127    def grpc_python_deps(self):
128        pass
129
130
131# Parse git hashes from bazel/grpc_deps.bzl {new_}http_archive rules
132with open(os.path.join('bazel', 'grpc_deps.bzl'), 'r') as f:
133    names_and_urls = {}
134    eval_state = BazelEvalState(names_and_urls)
135    bazel_file = f.read()
136
137# grpc_deps.bzl only defines 'grpc_deps' and 'grpc_test_only_deps', add these
138# lines to call them.
139bazel_file += '\ngrpc_deps()\n'
140bazel_file += '\ngrpc_test_only_deps()\n'
141build_rules = {
142    'native': eval_state,
143    'http_archive': lambda **args: eval_state.http_archive(**args),
144    'load': lambda a, b: None,
145    'git_repository': lambda **args: eval_state.git_repository(**args),
146    'grpc_python_deps': lambda: None,
147}
148exec(bazel_file) in build_rules
149for name in _GRPC_DEP_NAMES:
150    assert name in names_and_urls.keys()
151assert len(_GRPC_DEP_NAMES) == len(names_and_urls.keys())
152
153# There are some "bazel-only" deps that are exceptions to this sanity check,
154# we don't require that there is a corresponding git module for these.
155names_without_bazel_only_deps = names_and_urls.keys()
156for dep_name in _GRPC_BAZEL_ONLY_DEPS:
157    names_without_bazel_only_deps.remove(dep_name)
158archive_urls = [names_and_urls[name] for name in names_without_bazel_only_deps]
159workspace_git_hashes = {
160    re.search(git_hash_pattern, url).group() for url in archive_urls
161}
162if len(workspace_git_hashes) == 0:
163    print("(Likely) parse error, did not find any bazel git dependencies.")
164    sys.exit(1)
165
166# Validate the equivalence of the git submodules and Bazel git dependencies. The
167# condition we impose is that there is a git submodule for every dependency in
168# the workspace, but not necessarily conversely. E.g. Bloaty is a dependency
169# not used by any of the targets built by Bazel.
170if len(workspace_git_hashes - git_submodule_hashes) > 0:
171    print(
172        "Found discrepancies between git submodules and Bazel WORKSPACE dependencies"
173    )
174    print("workspace_git_hashes: %s" % workspace_git_hashes)
175    print("git_submodule_hashes: %s" % git_submodule_hashes)
176    print("workspace_git_hashes - git_submodule_hashes: %s" %
177          (workspace_git_hashes - git_submodule_hashes))
178    sys.exit(1)
179
180# Also check that we can override each dependency
181for name in _GRPC_DEP_NAMES:
182    names_and_urls_with_overridden_name = {}
183    state = BazelEvalState(names_and_urls_with_overridden_name,
184                           overridden_name=name)
185    rules = {
186        'native': state,
187        'http_archive': lambda **args: state.http_archive(**args),
188        'load': lambda a, b: None,
189        'git_repository': lambda **args: state.git_repository(**args),
190        'grpc_python_deps': lambda *args, **kwargs: None,
191    }
192    exec(bazel_file) in rules
193    assert name not in names_and_urls_with_overridden_name.keys()
194
195sys.exit(0)
196