• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2# Copyright 2020 The gRPC Authors
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8#     http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16import subprocess
17import yaml
18import xml.etree.ElementTree as ET
19import os
20import sys
21import build_cleaner
22
23_ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
24os.chdir(_ROOT)
25
26
27def _bazel_query_xml_tree(query):
28    """Get xml output of bazel query invocation, parsed as XML tree"""
29    output = subprocess.check_output(
30        ['tools/bazel', 'query', '--noimplicit_deps', '--output', 'xml', query])
31    return ET.fromstring(output)
32
33
34def _rule_dict_from_xml_node(rule_xml_node):
35    result = {
36        'class': rule_xml_node.attrib.get('class'),
37        'name': rule_xml_node.attrib.get('name'),
38        'srcs': [],
39        'hdrs': [],
40        'deps': [],
41        'data': [],
42        'tags': [],
43        'args': [],
44        'generator_function': None,
45        'size': None,
46        'flaky': False,
47    }
48    for child in rule_xml_node:
49        # all the metadata we want is stored under "list" tags
50        if child.tag == 'list':
51            list_name = child.attrib['name']
52            if list_name in ['srcs', 'hdrs', 'deps', 'data', 'tags', 'args']:
53                result[list_name] += [item.attrib['value'] for item in child]
54        if child.tag == 'string':
55            string_name = child.attrib['name']
56            if string_name in ['generator_function', 'size']:
57                result[string_name] = child.attrib['value']
58        if child.tag == 'boolean':
59            bool_name = child.attrib['name']
60            if bool_name in ['flaky']:
61                result[bool_name] = child.attrib['value'] == 'true'
62    return result
63
64
65def _extract_rules_from_bazel_xml(xml_tree):
66    result = {}
67    for child in xml_tree:
68        if child.tag == 'rule':
69            rule_dict = _rule_dict_from_xml_node(child)
70            rule_clazz = rule_dict['class']
71            rule_name = rule_dict['name']
72            if rule_clazz in [
73                    'cc_library', 'cc_binary', 'cc_test', 'cc_proto_library',
74                    'proto_library'
75            ]:
76                if rule_name in result:
77                    raise Exception('Rule %s already present' % rule_name)
78                result[rule_name] = rule_dict
79    return result
80
81
82def _get_bazel_label(target_name):
83    if ':' in target_name:
84        return '//%s' % target_name
85    else:
86        return '//:%s' % target_name
87
88
89def _extract_source_file_path(label):
90    """Gets relative path to source file from bazel deps listing"""
91    if label.startswith('//'):
92        label = label[len('//'):]
93    # labels in form //:src/core/lib/surface/call_test_only.h
94    if label.startswith(':'):
95        label = label[len(':'):]
96    # labels in form //test/core/util:port.cc
97    label = label.replace(':', '/')
98    return label
99
100
101def _extract_public_headers(bazel_rule):
102    """Gets list of public headers from a bazel rule"""
103    result = []
104    for dep in bazel_rule['hdrs']:
105        if dep.startswith('//:include/') and dep.endswith('.h'):
106            result.append(_extract_source_file_path(dep))
107    return list(sorted(result))
108
109
110def _extract_nonpublic_headers(bazel_rule):
111    """Gets list of non-public headers from a bazel rule"""
112    result = []
113    for dep in bazel_rule['hdrs']:
114        if dep.startswith('//') and not dep.startswith(
115                '//:include/') and dep.endswith('.h'):
116            result.append(_extract_source_file_path(dep))
117    return list(sorted(result))
118
119
120def _extract_sources(bazel_rule):
121    """Gets list of source files from a bazel rule"""
122    result = []
123    for dep in bazel_rule['srcs']:
124        if dep.startswith('//') and (dep.endswith('.cc') or dep.endswith('.c')
125                                     or dep.endswith('.proto')):
126            result.append(_extract_source_file_path(dep))
127    return list(sorted(result))
128
129
130def _extract_deps(bazel_rule):
131    """Gets list of deps from from a bazel rule"""
132    return list(sorted(bazel_rule['deps']))
133
134
135def _create_target_from_bazel_rule(target_name, bazel_rules):
136    # extract the deps from bazel
137    bazel_rule = bazel_rules[_get_bazel_label(target_name)]
138    result = {
139        'name': target_name,
140        '_PUBLIC_HEADERS_BAZEL': _extract_public_headers(bazel_rule),
141        '_HEADERS_BAZEL': _extract_nonpublic_headers(bazel_rule),
142        '_SRC_BAZEL': _extract_sources(bazel_rule),
143        '_DEPS_BAZEL': _extract_deps(bazel_rule),
144    }
145    return result
146
147
148def _sort_by_build_order(lib_names, lib_dict, deps_key_name, verbose=False):
149    """Sort library names to form correct build order. Use metadata from lib_dict"""
150    # we find correct build order by performing a topological sort
151    # expected output: if library B depends on A, A should be listed first
152
153    # all libs that are not in the dictionary are considered external.
154    external_deps = list(
155        sorted(filter(lambda lib_name: lib_name not in lib_dict, lib_names)))
156    if verbose:
157        print('topo_ordering ' + str(lib_names))
158        print('    external_deps ' + str(external_deps))
159
160    result = list(external_deps)  # external deps will be listed first
161    while len(result) < len(lib_names):
162        more_results = []
163        for lib in lib_names:
164            if lib not in result:
165                dep_set = set(lib_dict[lib].get(deps_key_name, []))
166                dep_set = dep_set.intersection(lib_names)
167                # if lib only depends on what's already built, add it to the results
168                if not dep_set.difference(set(result)):
169                    more_results.append(lib)
170        if not more_results:
171            raise Exception(
172                'Cannot sort topologically, there seems to be a cyclic dependency'
173            )
174        if verbose:
175            print('    adding ' + str(more_results))
176        result = result + list(
177            sorted(more_results
178                  ))  # when build order doesn't matter, sort lexicographically
179    return result
180
181
182# TODO(jtattermusch): deduplicate with transitive_dependencies.py (which has a slightly different logic)
183def _populate_transitive_deps(bazel_rules):
184    """Add 'transitive_deps' field for each of the rules"""
185    transitive_deps = {}
186    for rule_name in bazel_rules.iterkeys():
187        transitive_deps[rule_name] = set(bazel_rules[rule_name]['deps'])
188
189    while True:
190        deps_added = 0
191        for rule_name in bazel_rules.iterkeys():
192            old_deps = transitive_deps[rule_name]
193            new_deps = set(old_deps)
194            for dep_name in old_deps:
195                new_deps.update(transitive_deps.get(dep_name, set()))
196            deps_added += len(new_deps) - len(old_deps)
197            transitive_deps[rule_name] = new_deps
198        # if none of the transitive dep sets has changed, we're done
199        if deps_added == 0:
200            break
201
202    for rule_name, bazel_rule in bazel_rules.iteritems():
203        bazel_rule['transitive_deps'] = list(sorted(transitive_deps[rule_name]))
204
205
206def _external_dep_name_from_bazel_dependency(bazel_dep):
207    """Returns name of dependency if external bazel dependency is provided or None"""
208    if bazel_dep.startswith('@com_google_absl//'):
209        # special case for add dependency on one of the absl libraries (there is not just one absl library)
210        prefixlen = len('@com_google_absl//')
211        return bazel_dep[prefixlen:]
212    elif bazel_dep == '//external:upb_lib':
213        return 'upb'
214    elif bazel_dep == '//external:benchmark':
215        return 'benchmark'
216    else:
217        # all the other external deps such as gflags, protobuf, cares, zlib
218        # don't need to be listed explicitly, they are handled automatically
219        # by the build system (make, cmake)
220        return None
221
222
223def _expand_intermediate_deps(target_dict, public_dep_names, bazel_rules):
224    # Some of the libraries defined by bazel won't be exposed in build.yaml
225    # We call these "intermediate" dependencies. This method expands
226    # the intermediate deps for given target (populates library's
227    # headers, sources and dicts as if the intermediate dependency never existed)
228
229    # use this dictionary to translate from bazel labels to dep names
230    bazel_label_to_dep_name = {}
231    for dep_name in public_dep_names:
232        bazel_label_to_dep_name[_get_bazel_label(dep_name)] = dep_name
233
234    target_name = target_dict['name']
235    bazel_deps = target_dict['_DEPS_BAZEL']
236
237    # initial values
238    public_headers = set(target_dict['_PUBLIC_HEADERS_BAZEL'])
239    headers = set(target_dict['_HEADERS_BAZEL'])
240    src = set(target_dict['_SRC_BAZEL'])
241    deps = set()
242
243    expansion_blacklist = set()
244    to_expand = set(bazel_deps)
245    while to_expand:
246
247        # start with the last dependency to be built
248        build_order = _sort_by_build_order(list(to_expand), bazel_rules,
249                                           'transitive_deps')
250
251        bazel_dep = build_order[-1]
252        to_expand.remove(bazel_dep)
253
254        is_public = bazel_dep in bazel_label_to_dep_name
255        external_dep_name_maybe = _external_dep_name_from_bazel_dependency(
256            bazel_dep)
257
258        if is_public:
259            # this is not an intermediate dependency we so we add it
260            # to the list of public dependencies to the list, in the right format
261            deps.add(bazel_label_to_dep_name[bazel_dep])
262
263            # we do not want to expand any intermediate libraries that are already included
264            # by the dependency we just added
265            expansion_blacklist.update(
266                bazel_rules[bazel_dep]['transitive_deps'])
267
268        elif external_dep_name_maybe:
269            deps.add(external_dep_name_maybe)
270
271        elif bazel_dep.startswith(
272                '//external:') or not bazel_dep.startswith('//'):
273            # all the other external deps can be skipped
274            pass
275
276        elif bazel_dep in expansion_blacklist:
277            # do not expand if a public dependency that depends on this has already been expanded
278            pass
279
280        else:
281            if bazel_dep in bazel_rules:
282                # this is an intermediate library, expand it
283                public_headers.update(
284                    _extract_public_headers(bazel_rules[bazel_dep]))
285                headers.update(
286                    _extract_nonpublic_headers(bazel_rules[bazel_dep]))
287                src.update(_extract_sources(bazel_rules[bazel_dep]))
288
289                new_deps = _extract_deps(bazel_rules[bazel_dep])
290                to_expand.update(new_deps)
291            else:
292                raise Exception(bazel_dep + ' not in bazel_rules')
293
294    # make the 'deps' field transitive, but only list non-intermediate deps and selected external deps
295    bazel_transitive_deps = bazel_rules[_get_bazel_label(
296        target_name)]['transitive_deps']
297    for transitive_bazel_dep in bazel_transitive_deps:
298        public_name = bazel_label_to_dep_name.get(transitive_bazel_dep, None)
299        if public_name:
300            deps.add(public_name)
301        external_dep_name_maybe = _external_dep_name_from_bazel_dependency(
302            transitive_bazel_dep)
303        if external_dep_name_maybe:
304            # expanding all absl libraries is technically correct but creates too much noise
305            if not external_dep_name_maybe.startswith('absl'):
306                deps.add(external_dep_name_maybe)
307
308    target_dict['public_headers'] = list(sorted(public_headers))
309    target_dict['headers'] = list(sorted(headers))
310    target_dict['src'] = list(sorted(src))
311    target_dict['deps'] = list(sorted(deps))
312
313
314def _generate_build_metadata(build_extra_metadata, bazel_rules):
315    lib_names = build_extra_metadata.keys()
316    result = {}
317
318    for lib_name in lib_names:
319        lib_dict = _create_target_from_bazel_rule(lib_name, bazel_rules)
320
321        _expand_intermediate_deps(lib_dict, lib_names, bazel_rules)
322
323        # populate extra properties from build metadata
324        lib_dict.update(build_extra_metadata.get(lib_name, {}))
325
326        # store to results
327        result[lib_name] = lib_dict
328
329    # rename some targets to something else
330    # this needs to be made after we're done with most of processing logic
331    # otherwise the already-renamed libraries will have different names than expected
332    for lib_name in lib_names:
333        to_name = build_extra_metadata.get(lib_name, {}).get('_RENAME', None)
334        if to_name:
335            # store lib under the new name and also change its 'name' property
336            if to_name in result:
337                raise Exception('Cannot rename target ' + lib_name + ', ' +
338                                to_name + ' already exists.')
339            lib_dict = result.pop(lib_name)
340            lib_dict['name'] = to_name
341            result[to_name] = lib_dict
342
343            # dep names need to be updated as well
344            for lib_dict_to_update in result.values():
345                lib_dict_to_update['deps'] = list(
346                    map(lambda dep: to_name if dep == lib_name else dep,
347                        lib_dict_to_update['deps']))
348
349    # make sure deps are listed in reverse topological order (e.g. "grpc gpr" and not "gpr grpc")
350    for lib_dict in result.itervalues():
351        lib_dict['deps'] = list(
352            reversed(_sort_by_build_order(lib_dict['deps'], result, 'deps')))
353
354    return result
355
356
357def _convert_to_build_yaml_like(lib_dict):
358    lib_names = list(
359        filter(
360            lambda lib_name: lib_dict[lib_name].get('_TYPE', 'library') ==
361            'library', lib_dict.keys()))
362    target_names = list(
363        filter(
364            lambda lib_name: lib_dict[lib_name].get('_TYPE', 'library') ==
365            'target', lib_dict.keys()))
366    test_names = list(
367        filter(
368            lambda lib_name: lib_dict[lib_name].get('_TYPE', 'library') ==
369            'test', lib_dict.keys()))
370
371    # list libraries and targets in predefined order
372    lib_list = list(map(lambda lib_name: lib_dict[lib_name], lib_names))
373    target_list = list(map(lambda lib_name: lib_dict[lib_name], target_names))
374    test_list = list(map(lambda lib_name: lib_dict[lib_name], test_names))
375
376    # get rid of temporary private fields prefixed with "_" and some other useless fields
377    for lib in lib_list:
378        for field_to_remove in filter(lambda k: k.startswith('_'), lib.keys()):
379            lib.pop(field_to_remove, None)
380    for target in target_list:
381        for field_to_remove in filter(lambda k: k.startswith('_'),
382                                      target.keys()):
383            target.pop(field_to_remove, None)
384        target.pop('public_headers',
385                   None)  # public headers make no sense for targets
386    for test in test_list:
387        for field_to_remove in filter(lambda k: k.startswith('_'), test.keys()):
388            test.pop(field_to_remove, None)
389        test.pop('public_headers',
390                 None)  # public headers make no sense for tests
391
392    build_yaml_like = {
393        'libs': lib_list,
394        'filegroups': [],
395        'targets': target_list,
396        'tests': test_list,
397    }
398    return build_yaml_like
399
400
401def _extract_cc_tests(bazel_rules):
402    """Gets list of cc_test tests from bazel rules"""
403    result = []
404    for bazel_rule in bazel_rules.itervalues():
405        if bazel_rule['class'] == 'cc_test':
406            test_name = bazel_rule['name']
407            if test_name.startswith('//'):
408                prefixlen = len('//')
409                result.append(test_name[prefixlen:])
410    return list(sorted(result))
411
412
413def _filter_cc_tests(tests):
414    """Filters out tests that we don't want or we cannot build them reasonably"""
415
416    # most qps tests are autogenerated, we are fine without them
417    tests = list(
418        filter(lambda test: not test.startswith('test/cpp/qps:'), tests))
419
420    # we have trouble with census dependency outside of bazel
421    tests = list(
422        filter(lambda test: not test.startswith('test/cpp/ext/filters/census:'),
423               tests))
424    tests = list(
425        filter(
426            lambda test: not test.startswith(
427                'test/cpp/microbenchmarks:bm_opencensus_plugin'), tests))
428
429    # missing opencensus/stats/stats.h
430    tests = list(
431        filter(
432            lambda test: not test.startswith(
433                'test/cpp/end2end:server_load_reporting_end2end_test'), tests))
434    tests = list(
435        filter(
436            lambda test: not test.startswith(
437                'test/cpp/server/load_reporter:lb_load_reporter_test'), tests))
438
439    # The test uses --running_under_bazel cmdline argument
440    # To avoid the trouble needing to adjust it, we just skip the test
441    tests = list(
442        filter(
443            lambda test: not test.startswith(
444                'test/cpp/naming:resolver_component_tests_runner_invoker'),
445            tests))
446
447    # the test requires 'client_crash_test_server' to be built
448    tests = list(
449        filter(
450            lambda test: not test.startswith('test/cpp/end2end:time_change_test'
451                                            ), tests))
452
453    # the test requires 'client_crash_test_server' to be built
454    tests = list(
455        filter(
456            lambda test: not test.startswith(
457                'test/cpp/end2end:client_crash_test'), tests))
458
459    # the test requires 'server_crash_test_client' to be built
460    tests = list(
461        filter(
462            lambda test: not test.startswith(
463                'test/cpp/end2end:server_crash_test'), tests))
464
465    # test never existed under build.yaml and it fails -> skip it
466    tests = list(
467        filter(
468            lambda test: not test.startswith(
469                'test/core/tsi:ssl_session_cache_test'), tests))
470
471    return tests
472
473
474def _generate_build_extra_metadata_for_tests(tests, bazel_rules):
475    test_metadata = {}
476    for test in tests:
477        test_dict = {'build': 'test', '_TYPE': 'target'}
478
479        bazel_rule = bazel_rules[_get_bazel_label(test)]
480
481        bazel_tags = bazel_rule['tags']
482        if 'manual' in bazel_tags:
483            # don't run the tests marked as "manual"
484            test_dict['run'] = False
485
486        if bazel_rule['flaky']:
487            # don't run tests that are marked as "flaky" under bazel
488            # because that would only add noise for the run_tests.py tests
489            # and seeing more failures for tests that we already know are flaky
490            # doesn't really help anything
491            test_dict['run'] = False
492
493        if 'no_uses_polling' in bazel_tags:
494            test_dict['uses_polling'] = False
495
496        if 'grpc_fuzzer' == bazel_rule['generator_function']:
497            # currently we hand-list fuzzers instead of generating them automatically
498            # because there's no way to obtain maxlen property from bazel BUILD file.
499            print('skipping fuzzer ' + test)
500            continue
501
502        # if any tags that restrict platform compatibility are present,
503        # generate the "platforms" field accordingly
504        # TODO(jtattermusch): there is also a "no_linux" tag, but we cannot take
505        # it into account as it is applied by grpc_cc_test when poller expansion
506        # is made (for tests where uses_polling=True). So for now, we just
507        # assume all tests are compatible with linux and ignore the "no_linux" tag
508        # completely.
509        known_platform_tags = set(['no_windows', 'no_mac'])
510        if set(bazel_tags).intersection(known_platform_tags):
511            platforms = []
512            # assume all tests are compatible with linux and posix
513            platforms.append('linux')
514            platforms.append(
515                'posix')  # there is no posix-specific tag in bazel BUILD
516            if not 'no_mac' in bazel_tags:
517                platforms.append('mac')
518            if not 'no_windows' in bazel_tags:
519                platforms.append('windows')
520            test_dict['platforms'] = platforms
521
522        if '//external:benchmark' in bazel_rule['transitive_deps']:
523            test_dict['benchmark'] = True
524            test_dict['defaults'] = 'benchmark'
525
526        cmdline_args = bazel_rule['args']
527        if cmdline_args:
528            test_dict['args'] = list(cmdline_args)
529
530        uses_gtest = '//external:gtest' in bazel_rule['transitive_deps']
531        if uses_gtest:
532            test_dict['gtest'] = True
533
534        if test.startswith('test/cpp') or uses_gtest:
535            test_dict['language'] = 'c++'
536
537        elif test.startswith('test/core'):
538            test_dict['language'] = 'c'
539        else:
540            raise Exception('wrong test' + test)
541
542        # short test name without the path.
543        # There can be name collisions, but we will resolve them later
544        simple_test_name = os.path.basename(_extract_source_file_path(test))
545        test_dict['_RENAME'] = simple_test_name
546
547        test_metadata[test] = test_dict
548
549    # detect duplicate test names
550    tests_by_simple_name = {}
551    for test_name, test_dict in test_metadata.iteritems():
552        simple_test_name = test_dict['_RENAME']
553        if not simple_test_name in tests_by_simple_name:
554            tests_by_simple_name[simple_test_name] = []
555        tests_by_simple_name[simple_test_name].append(test_name)
556
557    # choose alternative names for tests with a name collision
558    for collision_list in tests_by_simple_name.itervalues():
559        if len(collision_list) > 1:
560            for test_name in collision_list:
561                long_name = test_name.replace('/', '_').replace(':', '_')
562                print(
563                    'short name of "%s" collides with another test, renaming to %s'
564                    % (test_name, long_name))
565                test_metadata[test_name]['_RENAME'] = long_name
566
567    return test_metadata
568
569
570# extra metadata that will be used to construct build.yaml
571# there are mostly extra properties that we weren't able to obtain from the bazel build
572# _TYPE: whether this is library, target or test
573# _RENAME: whether this target should be renamed to a different name (to match expectations of make and cmake builds)
574# NOTE: secure is 'check' by default, so setting secure = False below does matter
575_BUILD_EXTRA_METADATA = {
576    'third_party/address_sorting:address_sorting': {
577        'language': 'c',
578        'build': 'all',
579        'secure': False,
580        '_RENAME': 'address_sorting'
581    },
582    'gpr': {
583        'language': 'c',
584        'build': 'all',
585        'secure': False
586    },
587    'grpc': {
588        'language': 'c',
589        'build': 'all',
590        'baselib': True,
591        'secure': True,
592        'deps_linkage': 'static',
593        'dll': True,
594        'generate_plugin_registry': True
595    },
596    'grpc++': {
597        'language': 'c++',
598        'build': 'all',
599        'baselib': True,
600        'dll': True
601    },
602    'grpc++_alts': {
603        'language': 'c++',
604        'build': 'all',
605        'baselib': True
606    },
607    'grpc++_error_details': {
608        'language': 'c++',
609        'build': 'all'
610    },
611    'grpc++_reflection': {
612        'language': 'c++',
613        'build': 'all'
614    },
615    'grpc++_unsecure': {
616        'language': 'c++',
617        'build': 'all',
618        'baselib': True,
619        'secure': False,
620        'dll': True
621    },
622    # TODO(jtattermusch): do we need to set grpc_csharp_ext's LDFLAGS for wrapping memcpy in the same way as in build.yaml?
623    'grpc_csharp_ext': {
624        'language': 'c',
625        'build': 'all',
626        'deps_linkage': 'static',
627        'dll': 'only'
628    },
629    'grpc_unsecure': {
630        'language': 'c',
631        'build': 'all',
632        'baselib': True,
633        'secure': False,
634        'deps_linkage': 'static',
635        'dll': True,
636        'generate_plugin_registry': True
637    },
638    'grpcpp_channelz': {
639        'language': 'c++',
640        'build': 'all'
641    },
642    'grpc++_test': {
643        'language': 'c++',
644        'build': 'private',
645    },
646    'src/compiler:grpc_plugin_support': {
647        'language': 'c++',
648        'build': 'protoc',
649        'secure': False,
650        '_RENAME': 'grpc_plugin_support'
651    },
652    'src/compiler:grpc_cpp_plugin': {
653        'language': 'c++',
654        'build': 'protoc',
655        'secure': False,
656        '_TYPE': 'target',
657        '_RENAME': 'grpc_cpp_plugin'
658    },
659    'src/compiler:grpc_csharp_plugin': {
660        'language': 'c++',
661        'build': 'protoc',
662        'secure': False,
663        '_TYPE': 'target',
664        '_RENAME': 'grpc_csharp_plugin'
665    },
666    'src/compiler:grpc_node_plugin': {
667        'language': 'c++',
668        'build': 'protoc',
669        'secure': False,
670        '_TYPE': 'target',
671        '_RENAME': 'grpc_node_plugin'
672    },
673    'src/compiler:grpc_objective_c_plugin': {
674        'language': 'c++',
675        'build': 'protoc',
676        'secure': False,
677        '_TYPE': 'target',
678        '_RENAME': 'grpc_objective_c_plugin'
679    },
680    'src/compiler:grpc_php_plugin': {
681        'language': 'c++',
682        'build': 'protoc',
683        'secure': False,
684        '_TYPE': 'target',
685        '_RENAME': 'grpc_php_plugin'
686    },
687    'src/compiler:grpc_python_plugin': {
688        'language': 'c++',
689        'build': 'protoc',
690        'secure': False,
691        '_TYPE': 'target',
692        '_RENAME': 'grpc_python_plugin'
693    },
694    'src/compiler:grpc_ruby_plugin': {
695        'language': 'c++',
696        'build': 'protoc',
697        'secure': False,
698        '_TYPE': 'target',
699        '_RENAME': 'grpc_ruby_plugin'
700    },
701
702    # TODO(jtattermusch): consider adding grpc++_core_stats
703
704    # test support libraries
705    'test/core/util:grpc_test_util': {
706        'language': 'c',
707        'build': 'private',
708        '_RENAME': 'grpc_test_util'
709    },
710    'test/core/util:grpc_test_util_unsecure': {
711        'language': 'c',
712        'build': 'private',
713        'secure': False,
714        '_RENAME': 'grpc_test_util_unsecure'
715    },
716    # TODO(jtattermusch): consider adding grpc++_test_util_unsecure - it doesn't seem to be used by bazel build (don't forget to set secure: False)
717    'test/cpp/util:test_config': {
718        'language': 'c++',
719        'build': 'private',
720        '_RENAME': 'grpc++_test_config'
721    },
722    'test/cpp/util:test_util': {
723        'language': 'c++',
724        'build': 'private',
725        '_RENAME': 'grpc++_test_util'
726    },
727
728    # end2end test support libraries
729    'test/core/end2end:end2end_tests': {
730        'language': 'c',
731        'build': 'private',
732        'secure': True,
733        '_RENAME': 'end2end_tests'
734    },
735    'test/core/end2end:end2end_nosec_tests': {
736        'language': 'c',
737        'build': 'private',
738        'secure': False,
739        '_RENAME': 'end2end_nosec_tests'
740    },
741
742    # benchmark support libraries
743    'test/cpp/microbenchmarks:helpers': {
744        'language': 'c++',
745        'build': 'test',
746        'defaults': 'benchmark',
747        '_RENAME': 'benchmark_helpers'
748    },
749    'test/cpp/interop:interop_client': {
750        'language': 'c++',
751        'build': 'test',
752        'run': False,
753        '_TYPE': 'target',
754        '_RENAME': 'interop_client'
755    },
756    'test/cpp/interop:interop_server': {
757        'language': 'c++',
758        'build': 'test',
759        'run': False,
760        '_TYPE': 'target',
761        '_RENAME': 'interop_server'
762    },
763    'test/cpp/interop:xds_interop_client': {
764        'language': 'c++',
765        'build': 'test',
766        'run': False,
767        '_TYPE': 'target',
768        '_RENAME': 'xds_interop_client'
769    },
770    'test/cpp/interop:xds_interop_server': {
771        'language': 'c++',
772        'build': 'test',
773        'run': False,
774        '_TYPE': 'target',
775        '_RENAME': 'xds_interop_server'
776    },
777    'test/cpp/interop:http2_client': {
778        'language': 'c++',
779        'build': 'test',
780        'run': False,
781        '_TYPE': 'target',
782        '_RENAME': 'http2_client'
783    },
784    'test/cpp/qps:qps_json_driver': {
785        'language': 'c++',
786        'build': 'test',
787        'run': False,
788        '_TYPE': 'target',
789        '_RENAME': 'qps_json_driver'
790    },
791    'test/cpp/qps:qps_worker': {
792        'language': 'c++',
793        'build': 'test',
794        'run': False,
795        '_TYPE': 'target',
796        '_RENAME': 'qps_worker'
797    },
798    'test/cpp/util:grpc_cli': {
799        'language': 'c++',
800        'build': 'test',
801        'run': False,
802        '_TYPE': 'target',
803        '_RENAME': 'grpc_cli'
804    },
805
806    # TODO(jtattermusch): create_jwt and verify_jwt breaks distribtests because it depends on grpc_test_utils and thus requires tests to be built
807    # For now it's ok to disable them as these binaries aren't very useful anyway.
808    #'test/core/security:create_jwt': { 'language': 'c', 'build': 'tool', '_TYPE': 'target', '_RENAME': 'grpc_create_jwt' },
809    #'test/core/security:verify_jwt': { 'language': 'c', 'build': 'tool', '_TYPE': 'target', '_RENAME': 'grpc_verify_jwt' },
810
811    # TODO(jtattermusch): add remaining tools such as grpc_print_google_default_creds_token (they are not used by bazel build)
812
813    # Fuzzers
814    'test/core/security:alts_credentials_fuzzer': {
815        'language': 'c++',
816        'build': 'fuzzer',
817        'corpus_dirs': ['test/core/security/corpus/alts_credentials_corpus'],
818        'maxlen': 2048,
819        '_TYPE': 'target',
820        '_RENAME': 'alts_credentials_fuzzer'
821    },
822    'test/core/end2end/fuzzers:client_fuzzer': {
823        'language': 'c++',
824        'build': 'fuzzer',
825        'corpus_dirs': ['test/core/end2end/fuzzers/client_fuzzer_corpus'],
826        'maxlen': 2048,
827        'dict': 'test/core/end2end/fuzzers/hpack.dictionary',
828        '_TYPE': 'target',
829        '_RENAME': 'client_fuzzer'
830    },
831    'test/core/transport/chttp2:hpack_parser_fuzzer': {
832        'language': 'c++',
833        'build': 'fuzzer',
834        'corpus_dirs': ['test/core/transport/chttp2/hpack_parser_corpus'],
835        'maxlen': 512,
836        'dict': 'test/core/end2end/fuzzers/hpack.dictionary',
837        '_TYPE': 'target',
838        '_RENAME': 'hpack_parser_fuzzer_test'
839    },
840    'test/core/http:request_fuzzer': {
841        'language': 'c++',
842        'build': 'fuzzer',
843        'corpus_dirs': ['test/core/http/request_corpus'],
844        'maxlen': 2048,
845        '_TYPE': 'target',
846        '_RENAME': 'http_request_fuzzer_test'
847    },
848    'test/core/http:response_fuzzer': {
849        'language': 'c++',
850        'build': 'fuzzer',
851        'corpus_dirs': ['test/core/http/response_corpus'],
852        'maxlen': 2048,
853        '_TYPE': 'target',
854        '_RENAME': 'http_response_fuzzer_test'
855    },
856    'test/core/json:json_fuzzer': {
857        'language': 'c++',
858        'build': 'fuzzer',
859        'corpus_dirs': ['test/core/json/corpus'],
860        'maxlen': 512,
861        '_TYPE': 'target',
862        '_RENAME': 'json_fuzzer_test'
863    },
864    'test/core/nanopb:fuzzer_response': {
865        'language': 'c++',
866        'build': 'fuzzer',
867        'corpus_dirs': ['test/core/nanopb/corpus_response'],
868        'maxlen': 128,
869        '_TYPE': 'target',
870        '_RENAME': 'nanopb_fuzzer_response_test'
871    },
872    'test/core/nanopb:fuzzer_serverlist': {
873        'language': 'c++',
874        'build': 'fuzzer',
875        'corpus_dirs': ['test/core/nanopb/corpus_serverlist'],
876        'maxlen': 128,
877        '_TYPE': 'target',
878        '_RENAME': 'nanopb_fuzzer_serverlist_test'
879    },
880    'test/core/slice:percent_decode_fuzzer': {
881        'language': 'c++',
882        'build': 'fuzzer',
883        'corpus_dirs': ['test/core/slice/percent_decode_corpus'],
884        'maxlen': 32,
885        '_TYPE': 'target',
886        '_RENAME': 'percent_decode_fuzzer'
887    },
888    'test/core/slice:percent_encode_fuzzer': {
889        'language': 'c++',
890        'build': 'fuzzer',
891        'corpus_dirs': ['test/core/slice/percent_encode_corpus'],
892        'maxlen': 32,
893        '_TYPE': 'target',
894        '_RENAME': 'percent_encode_fuzzer'
895    },
896    'test/core/end2end/fuzzers:server_fuzzer': {
897        'language': 'c++',
898        'build': 'fuzzer',
899        'corpus_dirs': ['test/core/end2end/fuzzers/server_fuzzer_corpus'],
900        'maxlen': 2048,
901        'dict': 'test/core/end2end/fuzzers/hpack.dictionary',
902        '_TYPE': 'target',
903        '_RENAME': 'server_fuzzer'
904    },
905    'test/core/security:ssl_server_fuzzer': {
906        'language': 'c++',
907        'build': 'fuzzer',
908        'corpus_dirs': ['test/core/security/corpus/ssl_server_corpus'],
909        'maxlen': 2048,
910        '_TYPE': 'target',
911        '_RENAME': 'ssl_server_fuzzer'
912    },
913    'test/core/client_channel:uri_fuzzer_test': {
914        'language': 'c++',
915        'build': 'fuzzer',
916        'corpus_dirs': ['test/core/client_channel/uri_corpus'],
917        'maxlen': 128,
918        '_TYPE': 'target',
919        '_RENAME': 'uri_fuzzer_test'
920    },
921
922    # TODO(jtattermusch): these fuzzers had no build.yaml equivalent
923    # test/core/compression:message_compress_fuzzer
924    # test/core/compression:message_decompress_fuzzer
925    # test/core/compression:stream_compression_fuzzer
926    # test/core/compression:stream_decompression_fuzzer
927    # test/core/slice:b64_decode_fuzzer
928    # test/core/slice:b64_encode_fuzzer
929}
930
931# We need a complete picture of all the targets and dependencies we're interested in
932# so we run multiple bazel queries and merge the results.
933_BAZEL_DEPS_QUERIES = [
934    'deps("//test/...")',
935    'deps("//:all")',
936    'deps("//src/compiler/...")',
937    'deps("//src/proto/...")',
938]
939
940bazel_rules = {}
941for query in _BAZEL_DEPS_QUERIES:
942    bazel_rules.update(
943        _extract_rules_from_bazel_xml(_bazel_query_xml_tree(query)))
944
945_populate_transitive_deps(bazel_rules)
946
947tests = _filter_cc_tests(_extract_cc_tests(bazel_rules))
948test_metadata = _generate_build_extra_metadata_for_tests(tests, bazel_rules)
949
950all_metadata = {}
951all_metadata.update(_BUILD_EXTRA_METADATA)
952all_metadata.update(test_metadata)
953
954all_targets_dict = _generate_build_metadata(all_metadata, bazel_rules)
955build_yaml_like = _convert_to_build_yaml_like(all_targets_dict)
956
957# if a test uses source files from src/ directly, it's a little bit suspicious
958for tgt in build_yaml_like['targets']:
959    if tgt['build'] == 'test':
960        for src in tgt['src']:
961            if src.startswith('src/') and not src.endswith('.proto'):
962                print('source file from under "src/" tree used in test ' +
963                      tgt['name'] + ': ' + src)
964
965build_yaml_string = build_cleaner.cleaned_build_yaml_dict_as_string(
966    build_yaml_like)
967with open('build_autogenerated.yaml', 'w') as file:
968    file.write(build_yaml_string)
969