extract_metadata_from_bazel_xml.py 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123
  1. #!/usr/bin/env python3
  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. # Script to extract build metadata from bazel BUILD.
  16. # To avoid having two sources of truth for the build metadata (build
  17. # targets, source files, header files etc.), this script analyzes the contents
  18. # of bazel BUILD files and generates a YAML file (currently called
  19. # build_autogenerated.yaml). The format and semantics of the generated YAML files
  20. # is chosen to match the format of a "build.yaml" file, which used
  21. # to be build the source of truth for gRPC build before bazel became
  22. # the primary build system.
  23. # A good basic overview of the "build.yaml" format is available here:
  24. # https://github.com/grpc/grpc/blob/master/templates/README.md. Note that
  25. # while useful as an overview, the doc does not act as formal spec
  26. # (formal spec does not exist in fact) and the doc can be incomplete,
  27. # inaccurate or slightly out of date.
  28. # TODO(jtattermusch): In the future we want to get rid of the legacy build.yaml
  29. # format entirely or simplify it to a point where it becomes self-explanatory
  30. # and doesn't need any detailed documentation.
  31. import collections
  32. import os
  33. import re
  34. import subprocess
  35. import sys
  36. from typing import Any, Dict, Iterable, List, Optional
  37. import xml.etree.ElementTree as ET
  38. import build_cleaner
  39. import yaml
  40. BuildMetadata = Dict[str, Any]
  41. BuildDict = Dict[str, BuildMetadata]
  42. BuildYaml = Dict[str, Any]
  43. def _bazel_query_xml_tree(query: str) -> ET.Element:
  44. """Get xml output of bazel query invocation, parsed as XML tree"""
  45. output = subprocess.check_output(
  46. ['tools/bazel', 'query', '--noimplicit_deps', '--output', 'xml', query])
  47. return ET.fromstring(output)
  48. def _rule_dict_from_xml_node(rule_xml_node):
  49. """Converts XML node representing a rule (obtained from "bazel query --output xml") to a dictionary that contains all the metadata we will need."""
  50. result = {
  51. 'class': rule_xml_node.attrib.get('class'),
  52. 'name': rule_xml_node.attrib.get('name'),
  53. 'srcs': [],
  54. 'hdrs': [],
  55. 'deps': [],
  56. 'data': [],
  57. 'tags': [],
  58. 'args': [],
  59. 'generator_function': None,
  60. 'size': None,
  61. 'flaky': False,
  62. }
  63. for child in rule_xml_node:
  64. # all the metadata we want is stored under "list" tags
  65. if child.tag == 'list':
  66. list_name = child.attrib['name']
  67. if list_name in ['srcs', 'hdrs', 'deps', 'data', 'tags', 'args']:
  68. result[list_name] += [item.attrib['value'] for item in child]
  69. if child.tag == 'string':
  70. string_name = child.attrib['name']
  71. if string_name in ['generator_function', 'size']:
  72. result[string_name] = child.attrib['value']
  73. if child.tag == 'boolean':
  74. bool_name = child.attrib['name']
  75. if bool_name in ['flaky']:
  76. result[bool_name] = child.attrib['value'] == 'true'
  77. return result
  78. def _extract_rules_from_bazel_xml(xml_tree):
  79. """Extract bazel rules from an XML tree node obtained from "bazel query --output xml" command."""
  80. result = {}
  81. for child in xml_tree:
  82. if child.tag == 'rule':
  83. rule_dict = _rule_dict_from_xml_node(child)
  84. rule_clazz = rule_dict['class']
  85. rule_name = rule_dict['name']
  86. if rule_clazz in [
  87. 'cc_library',
  88. 'cc_binary',
  89. 'cc_test',
  90. 'cc_proto_library',
  91. 'proto_library',
  92. 'upb_proto_library',
  93. 'upb_proto_reflection_library',
  94. ]:
  95. if rule_name in result:
  96. raise Exception('Rule %s already present' % rule_name)
  97. result[rule_name] = rule_dict
  98. return result
  99. def _get_bazel_label(target_name: str) -> str:
  100. if ':' in target_name:
  101. return '//%s' % target_name
  102. else:
  103. return '//:%s' % target_name
  104. def _extract_source_file_path(label: str) -> str:
  105. """Gets relative path to source file from bazel deps listing"""
  106. if label.startswith('//'):
  107. label = label[len('//'):]
  108. # labels in form //:src/core/lib/surface/call_test_only.h
  109. if label.startswith(':'):
  110. label = label[len(':'):]
  111. # labels in form //test/core/util:port.cc
  112. label = label.replace(':', '/')
  113. return label
  114. def _extract_public_headers(bazel_rule: BuildMetadata) -> List[str]:
  115. """Gets list of public headers from a bazel rule"""
  116. result = []
  117. for dep in bazel_rule['hdrs']:
  118. if dep.startswith('//:include/') and dep.endswith('.h'):
  119. result.append(_extract_source_file_path(dep))
  120. return list(sorted(result))
  121. def _extract_nonpublic_headers(bazel_rule: BuildMetadata) -> List[str]:
  122. """Gets list of non-public headers from a bazel rule"""
  123. result = []
  124. for dep in bazel_rule['hdrs']:
  125. if dep.startswith('//') and not dep.startswith(
  126. '//:include/') and dep.endswith('.h'):
  127. result.append(_extract_source_file_path(dep))
  128. return list(sorted(result))
  129. def _extract_sources(bazel_rule: BuildMetadata) -> List[str]:
  130. """Gets list of source files from a bazel rule"""
  131. result = []
  132. for dep in bazel_rule['srcs']:
  133. if dep.startswith('//') and (dep.endswith('.cc') or dep.endswith('.c')
  134. or dep.endswith('.proto')):
  135. result.append(_extract_source_file_path(dep))
  136. return list(sorted(result))
  137. def _extract_deps(bazel_rule: BuildMetadata,
  138. bazel_rules: BuildDict) -> List[str]:
  139. """Gets list of deps from from a bazel rule"""
  140. return list(sorted(bazel_rule['deps']))
  141. def _create_target_from_bazel_rule(target_name: str,
  142. bazel_rules: BuildDict) -> BuildMetadata:
  143. """Create build.yaml-like target definition from bazel metadata"""
  144. bazel_rule = bazel_rules[_get_bazel_label(target_name)]
  145. # Create a template for our target from the bazel rule. Initially we only
  146. # populate some "private" fields with the original info we got from bazel
  147. # and only later we will populate the public fields (once we do some extra
  148. # postprocessing).
  149. result = {
  150. 'name': target_name,
  151. '_PUBLIC_HEADERS_BAZEL': _extract_public_headers(bazel_rule),
  152. '_HEADERS_BAZEL': _extract_nonpublic_headers(bazel_rule),
  153. '_SRC_BAZEL': _extract_sources(bazel_rule),
  154. '_DEPS_BAZEL': _extract_deps(bazel_rule, bazel_rules),
  155. 'public_headers': bazel_rule['_COLLAPSED_PUBLIC_HEADERS'],
  156. 'headers': bazel_rule['_COLLAPSED_HEADERS'],
  157. 'src': bazel_rule['_COLLAPSED_SRCS'],
  158. 'deps': bazel_rule['_COLLAPSED_DEPS'],
  159. }
  160. return result
  161. def _external_dep_name_from_bazel_dependency(bazel_dep: str) -> Optional[str]:
  162. """Returns name of dependency if external bazel dependency is provided or None"""
  163. if bazel_dep.startswith('@com_google_absl//'):
  164. # special case for add dependency on one of the absl libraries (there is not just one absl library)
  165. prefixlen = len('@com_google_absl//')
  166. return bazel_dep[prefixlen:]
  167. elif bazel_dep == '//external:upb_lib':
  168. return 'upb'
  169. elif bazel_dep == '//external:benchmark':
  170. return 'benchmark'
  171. elif bazel_dep == '//external:libssl':
  172. return 'libssl'
  173. else:
  174. # all the other external deps such as protobuf, cares, zlib
  175. # don't need to be listed explicitly, they are handled automatically
  176. # by the build system (make, cmake)
  177. return None
  178. def _compute_transitive_metadata(
  179. rule_name: str, bazel_rules: Any,
  180. bazel_label_to_dep_name: Dict[str, str]) -> None:
  181. """Computes the final build metadata for Bazel target with rule_name.
  182. The dependencies that will appear on the deps list are:
  183. * Public build targets including binaries and tests;
  184. * External targets, like absl, re2.
  185. All other intermediate dependencies will be merged, which means their
  186. source file, headers, etc. will be collected into one build target. This
  187. step of processing will greatly reduce the complexity of the generated
  188. build specifications for other build systems, like CMake, Make, setuptools.
  189. The final build metadata are:
  190. * _TRANSITIVE_DEPS: all the transitive dependencies including intermediate
  191. targets;
  192. * _COLLAPSED_DEPS: dependencies that fits our requirement above, and it
  193. will remove duplicated items and produce the shortest
  194. possible dependency list in alphabetical order;
  195. * _COLLAPSED_SRCS: the merged source files;
  196. * _COLLAPSED_PUBLIC_HEADERS: the merged public headers;
  197. * _COLLAPSED_HEADERS: the merged non-public headers;
  198. * _EXCLUDE_DEPS: intermediate targets to exclude when performing collapsing
  199. of sources and dependencies.
  200. For the collapsed_deps, the algorithm improved cases like:
  201. The result in the past:
  202. end2end_tests -> [grpc_test_util, grpc, gpr, address_sorting, upb]
  203. grpc_test_util -> [grpc, gpr, address_sorting, upb, ...]
  204. grpc -> [gpr, address_sorting, upb, ...]
  205. The result of the algorithm:
  206. end2end_tests -> [grpc_test_util]
  207. grpc_test_util -> [grpc]
  208. grpc -> [gpr, address_sorting, upb, ...]
  209. """
  210. bazel_rule = bazel_rules[rule_name]
  211. direct_deps = _extract_deps(bazel_rule, bazel_rules)
  212. transitive_deps = set()
  213. collapsed_deps = set()
  214. exclude_deps = set()
  215. collapsed_srcs = set(_extract_sources(bazel_rule))
  216. collapsed_public_headers = set(_extract_public_headers(bazel_rule))
  217. collapsed_headers = set(_extract_nonpublic_headers(bazel_rule))
  218. for dep in direct_deps:
  219. external_dep_name_maybe = _external_dep_name_from_bazel_dependency(dep)
  220. if dep in bazel_rules:
  221. # Descend recursively, but no need to do that for external deps
  222. if external_dep_name_maybe is None:
  223. if "_PROCESSING_DONE" not in bazel_rules[dep]:
  224. # This item is not processed before, compute now
  225. _compute_transitive_metadata(dep, bazel_rules,
  226. bazel_label_to_dep_name)
  227. transitive_deps.update(bazel_rules[dep].get(
  228. '_TRANSITIVE_DEPS', []))
  229. collapsed_deps.update(
  230. collapsed_deps, bazel_rules[dep].get('_COLLAPSED_DEPS', []))
  231. exclude_deps.update(bazel_rules[dep].get('_EXCLUDE_DEPS', []))
  232. # This dep is a public target, add it as a dependency
  233. if dep in bazel_label_to_dep_name:
  234. transitive_deps.update([bazel_label_to_dep_name[dep]])
  235. collapsed_deps.update(collapsed_deps,
  236. [bazel_label_to_dep_name[dep]])
  237. # Add all the transitive deps of our every public dep to exclude
  238. # list since we want to avoid building sources that are already
  239. # built by our dependencies
  240. exclude_deps.update(bazel_rules[dep]['_TRANSITIVE_DEPS'])
  241. continue
  242. # This dep is an external target, add it as a dependency
  243. if external_dep_name_maybe is not None:
  244. transitive_deps.update([external_dep_name_maybe])
  245. collapsed_deps.update(collapsed_deps, [external_dep_name_maybe])
  246. continue
  247. # Direct dependencies are part of transitive dependencies
  248. transitive_deps.update(direct_deps)
  249. # Calculate transitive public deps (needed for collapsing sources)
  250. transitive_public_deps = set(
  251. [x for x in transitive_deps if x in bazel_label_to_dep_name])
  252. # Remove intermediate targets that our public dependencies already depend
  253. # on. This is the step that further shorten the deps list.
  254. collapsed_deps = set([x for x in collapsed_deps if x not in exclude_deps])
  255. # Compute the final source files and headers for this build target whose
  256. # name is `rule_name` (input argument of this function).
  257. #
  258. # Imaging a public target PX has transitive deps [IA, IB, PY, IC, PZ]. PX,
  259. # PY and PZ are public build targets. And IA, IB, IC are intermediate
  260. # targets. In addition, PY depends on IC.
  261. #
  262. # Translate the condition into dependency graph:
  263. # PX -> [IA, IB, PY, IC, PZ]
  264. # PY -> [IC]
  265. # Public targets: [PX, PY, PZ]
  266. #
  267. # The collapsed dependencies of PX: [PY, PZ].
  268. # The excluded dependencies of X: [PY, IC, PZ].
  269. # (IC is excluded as a dependency of PX. It is already included in PY, hence
  270. # it would be redundant to include it again.)
  271. #
  272. # Target PX should include source files and headers of [PX, IA, IB] as final
  273. # build metadata.
  274. for dep in transitive_deps:
  275. if dep not in exclude_deps and dep not in transitive_public_deps:
  276. if dep in bazel_rules:
  277. collapsed_srcs.update(_extract_sources(bazel_rules[dep]))
  278. collapsed_public_headers.update(
  279. _extract_public_headers(bazel_rules[dep]))
  280. collapsed_headers.update(
  281. _extract_nonpublic_headers(bazel_rules[dep]))
  282. # This item is a "visited" flag
  283. bazel_rule['_PROCESSING_DONE'] = True
  284. # Following items are described in the docstinrg.
  285. bazel_rule['_TRANSITIVE_DEPS'] = list(sorted(transitive_deps))
  286. bazel_rule['_COLLAPSED_DEPS'] = list(sorted(collapsed_deps))
  287. bazel_rule['_COLLAPSED_SRCS'] = list(sorted(collapsed_srcs))
  288. bazel_rule['_COLLAPSED_PUBLIC_HEADERS'] = list(
  289. sorted(collapsed_public_headers))
  290. bazel_rule['_COLLAPSED_HEADERS'] = list(sorted(collapsed_headers))
  291. bazel_rule['_EXCLUDE_DEPS'] = list(sorted(exclude_deps))
  292. # TODO(jtattermusch): deduplicate with transitive_dependencies.py (which has a slightly different logic)
  293. # TODO(jtattermusch): This is done to avoid introducing too many intermediate
  294. # libraries into the build.yaml-based builds (which might in cause issues
  295. # building language-specific artifacts) and also because the libraries
  296. # in build.yaml-based build are generally considered units of distributions
  297. # (= public libraries that are visible to the user and are installable),
  298. # while in bazel builds it is customary to define larger number of smaller
  299. # "sublibraries". The need for elision (and expansion)
  300. # of intermediate libraries can be re-evaluated in the future.
  301. def _populate_transitive_metadata(bazel_rules: Any,
  302. public_dep_names: Iterable[str]) -> None:
  303. """Add 'transitive_deps' field for each of the rules"""
  304. # Create the map between Bazel label and public dependency name
  305. bazel_label_to_dep_name = {}
  306. for dep_name in public_dep_names:
  307. bazel_label_to_dep_name[_get_bazel_label(dep_name)] = dep_name
  308. # Make sure we reached all the Bazel rules
  309. # TODO(lidiz) potentially we could only update a subset of rules
  310. for rule_name in bazel_rules:
  311. if '_PROCESSING_DONE' not in bazel_rules[rule_name]:
  312. _compute_transitive_metadata(rule_name, bazel_rules,
  313. bazel_label_to_dep_name)
  314. def update_test_metadata_with_transitive_metadata(
  315. all_extra_metadata: BuildDict, bazel_rules: BuildDict) -> None:
  316. """Patches test build metadata with transitive metadata."""
  317. for lib_name, lib_dict in list(all_extra_metadata.items()):
  318. # Skip if it isn't not an test
  319. if lib_dict.get('build') != 'test' or lib_dict.get('_TYPE') != 'target':
  320. continue
  321. bazel_rule = bazel_rules[_get_bazel_label(lib_name)]
  322. if '//external:benchmark' in bazel_rule['_TRANSITIVE_DEPS']:
  323. lib_dict['benchmark'] = True
  324. lib_dict['defaults'] = 'benchmark'
  325. if '//external:gtest' in bazel_rule['_TRANSITIVE_DEPS']:
  326. lib_dict['gtest'] = True
  327. lib_dict['language'] = 'c++'
  328. def _get_transitive_protos(bazel_rules, t):
  329. que = [
  330. t,
  331. ]
  332. visited = set()
  333. ret = []
  334. while que:
  335. name = que.pop(0)
  336. rule = bazel_rules.get(name, None)
  337. if rule:
  338. for dep in rule['deps']:
  339. if dep not in visited:
  340. visited.add(dep)
  341. que.append(dep)
  342. for src in rule['srcs']:
  343. if src.endswith('.proto'):
  344. ret.append(src)
  345. return list(set(ret))
  346. def _expand_upb_proto_library_rules(bazel_rules):
  347. # Expand the .proto files from UPB proto library rules into the pre-generated
  348. # upb.h and upb.c files.
  349. GEN_UPB_ROOT = '//:src/core/ext/upb-generated/'
  350. GEN_UPBDEFS_ROOT = '//:src/core/ext/upbdefs-generated/'
  351. EXTERNAL_LINKS = [('@com_google_protobuf//', ':src/'),
  352. ('@com_google_googleapis//', ''),
  353. ('@com_github_cncf_udpa//', ''),
  354. ('@com_envoyproxy_protoc_gen_validate//', ''),
  355. ('@envoy_api//', ''), ('@opencensus_proto//', '')]
  356. for name, bazel_rule in bazel_rules.items():
  357. gen_func = bazel_rule.get('generator_function', None)
  358. if gen_func in ('grpc_upb_proto_library',
  359. 'grpc_upb_proto_reflection_library'):
  360. # get proto dependency
  361. deps = bazel_rule['deps']
  362. if len(deps) != 1:
  363. raise Exception(
  364. 'upb rule "{0}" should have 1 proto dependency but has "{1}"'
  365. .format(name, deps))
  366. # deps is not properly fetched from bazel query for upb_proto_library target
  367. # so add the upb dependency manually
  368. bazel_rule['deps'] = [
  369. '//external:upb_lib', '//external:upb_lib_descriptor',
  370. '//external:upb_generated_code_support__only_for_generated_code_do_not_use__i_give_permission_to_break_me'
  371. ]
  372. # populate the upb_proto_library rule with pre-generated upb headers
  373. # and sources using proto_rule
  374. protos = _get_transitive_protos(bazel_rules, deps[0])
  375. if len(protos) == 0:
  376. raise Exception(
  377. 'upb rule "{0}" should have at least one proto file.'.
  378. format(name))
  379. srcs = []
  380. hdrs = []
  381. for proto_src in protos:
  382. for external_link in EXTERNAL_LINKS:
  383. if proto_src.startswith(external_link[0]):
  384. proto_src = proto_src[len(external_link[0]) +
  385. len(external_link[1]):]
  386. break
  387. if proto_src.startswith('@'):
  388. raise Exception('"{0}" is unknown workspace.'.format(name))
  389. proto_src = _extract_source_file_path(proto_src)
  390. ext = '.upb' if gen_func == 'grpc_upb_proto_library' else '.upbdefs'
  391. root = GEN_UPB_ROOT if gen_func == 'grpc_upb_proto_library' else GEN_UPBDEFS_ROOT
  392. srcs.append(root + proto_src.replace('.proto', ext + '.c'))
  393. hdrs.append(root + proto_src.replace('.proto', ext + '.h'))
  394. bazel_rule['srcs'] = srcs
  395. bazel_rule['hdrs'] = hdrs
  396. def _generate_build_metadata(build_extra_metadata: BuildDict,
  397. bazel_rules: BuildDict) -> BuildDict:
  398. """Generate build metadata in build.yaml-like format bazel build metadata and build.yaml-specific "extra metadata"."""
  399. lib_names = list(build_extra_metadata.keys())
  400. result = {}
  401. for lib_name in lib_names:
  402. lib_dict = _create_target_from_bazel_rule(lib_name, bazel_rules)
  403. # populate extra properties from the build.yaml-specific "extra metadata"
  404. lib_dict.update(build_extra_metadata.get(lib_name, {}))
  405. # store to results
  406. result[lib_name] = lib_dict
  407. # Rename targets marked with "_RENAME" extra metadata.
  408. # This is mostly a cosmetic change to ensure that we end up with build.yaml target
  409. # names we're used to from the past (and also to avoid too long target names).
  410. # The rename step needs to be made after we're done with most of processing logic
  411. # otherwise the already-renamed libraries will have different names than expected
  412. for lib_name in lib_names:
  413. to_name = build_extra_metadata.get(lib_name, {}).get('_RENAME', None)
  414. if to_name:
  415. # store lib under the new name and also change its 'name' property
  416. if to_name in result:
  417. raise Exception('Cannot rename target ' + str(lib_name) + ', ' +
  418. str(to_name) + ' already exists.')
  419. lib_dict = result.pop(lib_name)
  420. lib_dict['name'] = to_name
  421. result[to_name] = lib_dict
  422. # dep names need to be updated as well
  423. for lib_dict_to_update in list(result.values()):
  424. lib_dict_to_update['deps'] = list([
  425. to_name if dep == lib_name else dep
  426. for dep in lib_dict_to_update['deps']
  427. ])
  428. return result
  429. def _convert_to_build_yaml_like(lib_dict: BuildMetadata) -> BuildYaml:
  430. lib_names = [
  431. lib_name for lib_name in list(lib_dict.keys())
  432. if lib_dict[lib_name].get('_TYPE', 'library') == 'library'
  433. ]
  434. target_names = [
  435. lib_name for lib_name in list(lib_dict.keys())
  436. if lib_dict[lib_name].get('_TYPE', 'library') == 'target'
  437. ]
  438. test_names = [
  439. lib_name for lib_name in list(lib_dict.keys())
  440. if lib_dict[lib_name].get('_TYPE', 'library') == 'test'
  441. ]
  442. # list libraries and targets in predefined order
  443. lib_list = [lib_dict[lib_name] for lib_name in lib_names]
  444. target_list = [lib_dict[lib_name] for lib_name in target_names]
  445. test_list = [lib_dict[lib_name] for lib_name in test_names]
  446. # get rid of temporary private fields prefixed with "_" and some other useless fields
  447. for lib in lib_list:
  448. for field_to_remove in [
  449. k for k in list(lib.keys()) if k.startswith('_')
  450. ]:
  451. lib.pop(field_to_remove, None)
  452. for target in target_list:
  453. for field_to_remove in [
  454. k for k in list(target.keys()) if k.startswith('_')
  455. ]:
  456. target.pop(field_to_remove, None)
  457. target.pop('public_headers',
  458. None) # public headers make no sense for targets
  459. for test in test_list:
  460. for field_to_remove in [
  461. k for k in list(test.keys()) if k.startswith('_')
  462. ]:
  463. test.pop(field_to_remove, None)
  464. test.pop('public_headers',
  465. None) # public headers make no sense for tests
  466. build_yaml_like = {
  467. 'libs': lib_list,
  468. 'filegroups': [],
  469. 'targets': target_list,
  470. 'tests': test_list,
  471. }
  472. return build_yaml_like
  473. def _extract_cc_tests(bazel_rules: BuildDict) -> List[str]:
  474. """Gets list of cc_test tests from bazel rules"""
  475. result = []
  476. for bazel_rule in list(bazel_rules.values()):
  477. if bazel_rule['class'] == 'cc_test':
  478. test_name = bazel_rule['name']
  479. if test_name.startswith('//'):
  480. prefixlen = len('//')
  481. result.append(test_name[prefixlen:])
  482. return list(sorted(result))
  483. def _exclude_unwanted_cc_tests(tests: List[str]) -> List[str]:
  484. """Filters out bazel tests that we don't want to run with other build systems or we cannot build them reasonably"""
  485. # most qps tests are autogenerated, we are fine without them
  486. tests = [test for test in tests if not test.startswith('test/cpp/qps:')]
  487. # microbenchmarks aren't needed for checking correctness
  488. tests = [
  489. test for test in tests
  490. if not test.startswith('test/cpp/microbenchmarks:')
  491. ]
  492. tests = [
  493. test for test in tests
  494. if not test.startswith('test/core/promise/benchmark:')
  495. ]
  496. # we have trouble with census dependency outside of bazel
  497. tests = [
  498. test for test in tests
  499. if not test.startswith('test/cpp/ext/filters/census:') and
  500. not test.startswith('test/core/xds:xds_channel_stack_modifier_test')
  501. ]
  502. # missing opencensus/stats/stats.h
  503. tests = [
  504. test for test in tests if not test.startswith(
  505. 'test/cpp/end2end:server_load_reporting_end2end_test')
  506. ]
  507. tests = [
  508. test for test in tests if not test.startswith(
  509. 'test/cpp/server/load_reporter:lb_load_reporter_test')
  510. ]
  511. # The test uses --running_under_bazel cmdline argument
  512. # To avoid the trouble needing to adjust it, we just skip the test
  513. tests = [
  514. test for test in tests if not test.startswith(
  515. 'test/cpp/naming:resolver_component_tests_runner_invoker')
  516. ]
  517. # the test requires 'client_crash_test_server' to be built
  518. tests = [
  519. test for test in tests
  520. if not test.startswith('test/cpp/end2end:time_change_test')
  521. ]
  522. # the test requires 'client_crash_test_server' to be built
  523. tests = [
  524. test for test in tests
  525. if not test.startswith('test/cpp/end2end:client_crash_test')
  526. ]
  527. # the test requires 'server_crash_test_client' to be built
  528. tests = [
  529. test for test in tests
  530. if not test.startswith('test/cpp/end2end:server_crash_test')
  531. ]
  532. # test never existed under build.yaml and it fails -> skip it
  533. tests = [
  534. test for test in tests
  535. if not test.startswith('test/core/tsi:ssl_session_cache_test')
  536. ]
  537. # the binary of this test does not get built with cmake
  538. tests = [
  539. test for test in tests
  540. if not test.startswith('test/cpp/util:channelz_sampler_test')
  541. ]
  542. # we don't need to generate fuzzers outside of bazel
  543. tests = [test for test in tests if not test.endswith('_fuzzer')]
  544. return tests
  545. def _generate_build_extra_metadata_for_tests(
  546. tests: List[str], bazel_rules: BuildDict) -> BuildDict:
  547. """For given tests, generate the "extra metadata" that we need for our "build.yaml"-like output. The extra metadata is generated from the bazel rule metadata by using a bunch of heuristics."""
  548. test_metadata = {}
  549. for test in tests:
  550. test_dict = {'build': 'test', '_TYPE': 'target'}
  551. bazel_rule = bazel_rules[_get_bazel_label(test)]
  552. bazel_tags = bazel_rule['tags']
  553. if 'manual' in bazel_tags:
  554. # don't run the tests marked as "manual"
  555. test_dict['run'] = False
  556. if bazel_rule['flaky']:
  557. # don't run tests that are marked as "flaky" under bazel
  558. # because that would only add noise for the run_tests.py tests
  559. # and seeing more failures for tests that we already know are flaky
  560. # doesn't really help anything
  561. test_dict['run'] = False
  562. if 'no_uses_polling' in bazel_tags:
  563. test_dict['uses_polling'] = False
  564. if 'grpc_fuzzer' == bazel_rule['generator_function']:
  565. # currently we hand-list fuzzers instead of generating them automatically
  566. # because there's no way to obtain maxlen property from bazel BUILD file.
  567. print(('skipping fuzzer ' + test))
  568. continue
  569. if 'bazel_only' in bazel_tags:
  570. continue
  571. # if any tags that restrict platform compatibility are present,
  572. # generate the "platforms" field accordingly
  573. # TODO(jtattermusch): there is also a "no_linux" tag, but we cannot take
  574. # it into account as it is applied by grpc_cc_test when poller expansion
  575. # is made (for tests where uses_polling=True). So for now, we just
  576. # assume all tests are compatible with linux and ignore the "no_linux" tag
  577. # completely.
  578. known_platform_tags = set(['no_windows', 'no_mac'])
  579. if set(bazel_tags).intersection(known_platform_tags):
  580. platforms = []
  581. # assume all tests are compatible with linux and posix
  582. platforms.append('linux')
  583. platforms.append(
  584. 'posix') # there is no posix-specific tag in bazel BUILD
  585. if not 'no_mac' in bazel_tags:
  586. platforms.append('mac')
  587. if not 'no_windows' in bazel_tags:
  588. platforms.append('windows')
  589. test_dict['platforms'] = platforms
  590. cmdline_args = bazel_rule['args']
  591. if cmdline_args:
  592. test_dict['args'] = list(cmdline_args)
  593. if test.startswith('test/cpp'):
  594. test_dict['language'] = 'c++'
  595. elif test.startswith('test/core'):
  596. test_dict['language'] = 'c'
  597. else:
  598. raise Exception('wrong test' + test)
  599. # short test name without the path.
  600. # There can be name collisions, but we will resolve them later
  601. simple_test_name = os.path.basename(_extract_source_file_path(test))
  602. test_dict['_RENAME'] = simple_test_name
  603. test_metadata[test] = test_dict
  604. # detect duplicate test names
  605. tests_by_simple_name = {}
  606. for test_name, test_dict in list(test_metadata.items()):
  607. simple_test_name = test_dict['_RENAME']
  608. if not simple_test_name in tests_by_simple_name:
  609. tests_by_simple_name[simple_test_name] = []
  610. tests_by_simple_name[simple_test_name].append(test_name)
  611. # choose alternative names for tests with a name collision
  612. for collision_list in list(tests_by_simple_name.values()):
  613. if len(collision_list) > 1:
  614. for test_name in collision_list:
  615. long_name = test_name.replace('/', '_').replace(':', '_')
  616. print((
  617. 'short name of "%s" collides with another test, renaming to %s'
  618. % (test_name, long_name)))
  619. test_metadata[test_name]['_RENAME'] = long_name
  620. return test_metadata
  621. def _detect_and_print_issues(build_yaml_like: BuildYaml) -> None:
  622. """Try detecting some unusual situations and warn about them."""
  623. for tgt in build_yaml_like['targets']:
  624. if tgt['build'] == 'test':
  625. for src in tgt['src']:
  626. if src.startswith('src/') and not src.endswith('.proto'):
  627. print(('source file from under "src/" tree used in test ' +
  628. tgt['name'] + ': ' + src))
  629. # extra metadata that will be used to construct build.yaml
  630. # there are mostly extra properties that we weren't able to obtain from the bazel build
  631. # _TYPE: whether this is library, target or test
  632. # _RENAME: whether this target should be renamed to a different name (to match expectations of make and cmake builds)
  633. _BUILD_EXTRA_METADATA = {
  634. 'third_party/address_sorting:address_sorting': {
  635. 'language': 'c',
  636. 'build': 'all',
  637. '_RENAME': 'address_sorting'
  638. },
  639. 'gpr': {
  640. 'language': 'c',
  641. 'build': 'all',
  642. },
  643. 'grpc': {
  644. 'language': 'c',
  645. 'build': 'all',
  646. 'baselib': True,
  647. 'generate_plugin_registry': True
  648. },
  649. 'grpc++': {
  650. 'language': 'c++',
  651. 'build': 'all',
  652. 'baselib': True,
  653. },
  654. 'grpc++_alts': {
  655. 'language': 'c++',
  656. 'build': 'all',
  657. 'baselib': True
  658. },
  659. 'grpc++_error_details': {
  660. 'language': 'c++',
  661. 'build': 'all'
  662. },
  663. 'grpc++_reflection': {
  664. 'language': 'c++',
  665. 'build': 'all'
  666. },
  667. 'grpc++_unsecure': {
  668. 'language': 'c++',
  669. 'build': 'all',
  670. 'baselib': True,
  671. },
  672. # TODO(jtattermusch): do we need to set grpc_csharp_ext's LDFLAGS for wrapping memcpy in the same way as in build.yaml?
  673. 'grpc_csharp_ext': {
  674. 'language': 'c',
  675. 'build': 'all',
  676. },
  677. 'grpc_unsecure': {
  678. 'language': 'c',
  679. 'build': 'all',
  680. 'baselib': True,
  681. 'generate_plugin_registry': True
  682. },
  683. 'grpcpp_channelz': {
  684. 'language': 'c++',
  685. 'build': 'all'
  686. },
  687. 'grpc++_test': {
  688. 'language': 'c++',
  689. 'build': 'private',
  690. },
  691. 'src/compiler:grpc_plugin_support': {
  692. 'language': 'c++',
  693. 'build': 'protoc',
  694. '_RENAME': 'grpc_plugin_support'
  695. },
  696. 'src/compiler:grpc_cpp_plugin': {
  697. 'language': 'c++',
  698. 'build': 'protoc',
  699. '_TYPE': 'target',
  700. '_RENAME': 'grpc_cpp_plugin'
  701. },
  702. 'src/compiler:grpc_csharp_plugin': {
  703. 'language': 'c++',
  704. 'build': 'protoc',
  705. '_TYPE': 'target',
  706. '_RENAME': 'grpc_csharp_plugin'
  707. },
  708. 'src/compiler:grpc_node_plugin': {
  709. 'language': 'c++',
  710. 'build': 'protoc',
  711. '_TYPE': 'target',
  712. '_RENAME': 'grpc_node_plugin'
  713. },
  714. 'src/compiler:grpc_objective_c_plugin': {
  715. 'language': 'c++',
  716. 'build': 'protoc',
  717. '_TYPE': 'target',
  718. '_RENAME': 'grpc_objective_c_plugin'
  719. },
  720. 'src/compiler:grpc_php_plugin': {
  721. 'language': 'c++',
  722. 'build': 'protoc',
  723. '_TYPE': 'target',
  724. '_RENAME': 'grpc_php_plugin'
  725. },
  726. 'src/compiler:grpc_python_plugin': {
  727. 'language': 'c++',
  728. 'build': 'protoc',
  729. '_TYPE': 'target',
  730. '_RENAME': 'grpc_python_plugin'
  731. },
  732. 'src/compiler:grpc_ruby_plugin': {
  733. 'language': 'c++',
  734. 'build': 'protoc',
  735. '_TYPE': 'target',
  736. '_RENAME': 'grpc_ruby_plugin'
  737. },
  738. # TODO(jtattermusch): consider adding grpc++_core_stats
  739. # test support libraries
  740. 'test/core/util:grpc_test_util': {
  741. 'language': 'c',
  742. 'build': 'private',
  743. '_RENAME': 'grpc_test_util'
  744. },
  745. 'test/core/util:grpc_test_util_unsecure': {
  746. 'language': 'c',
  747. 'build': 'private',
  748. '_RENAME': 'grpc_test_util_unsecure'
  749. },
  750. # 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)
  751. 'test/cpp/util:test_config': {
  752. 'language': 'c++',
  753. 'build': 'private',
  754. '_RENAME': 'grpc++_test_config'
  755. },
  756. 'test/cpp/util:test_util': {
  757. 'language': 'c++',
  758. 'build': 'private',
  759. '_RENAME': 'grpc++_test_util'
  760. },
  761. # end2end test support libraries
  762. 'test/core/end2end:end2end_tests': {
  763. 'language': 'c',
  764. 'build': 'private',
  765. '_RENAME': 'end2end_tests'
  766. },
  767. 'test/core/end2end:end2end_nosec_tests': {
  768. 'language': 'c',
  769. 'build': 'private',
  770. '_RENAME': 'end2end_nosec_tests'
  771. },
  772. # benchmark support libraries
  773. 'test/cpp/microbenchmarks:helpers': {
  774. 'language': 'c++',
  775. 'build': 'test',
  776. 'defaults': 'benchmark',
  777. '_RENAME': 'benchmark_helpers'
  778. },
  779. 'test/cpp/interop:interop_client': {
  780. 'language': 'c++',
  781. 'build': 'test',
  782. 'run': False,
  783. '_TYPE': 'target',
  784. '_RENAME': 'interop_client'
  785. },
  786. 'test/cpp/interop:interop_server': {
  787. 'language': 'c++',
  788. 'build': 'test',
  789. 'run': False,
  790. '_TYPE': 'target',
  791. '_RENAME': 'interop_server'
  792. },
  793. 'test/cpp/interop:xds_interop_client': {
  794. 'language': 'c++',
  795. 'build': 'test',
  796. 'run': False,
  797. '_TYPE': 'target',
  798. '_RENAME': 'xds_interop_client'
  799. },
  800. 'test/cpp/interop:xds_interop_server': {
  801. 'language': 'c++',
  802. 'build': 'test',
  803. 'run': False,
  804. '_TYPE': 'target',
  805. '_RENAME': 'xds_interop_server'
  806. },
  807. 'test/cpp/interop:http2_client': {
  808. 'language': 'c++',
  809. 'build': 'test',
  810. 'run': False,
  811. '_TYPE': 'target',
  812. '_RENAME': 'http2_client'
  813. },
  814. 'test/cpp/qps:qps_json_driver': {
  815. 'language': 'c++',
  816. 'build': 'test',
  817. 'run': False,
  818. '_TYPE': 'target',
  819. '_RENAME': 'qps_json_driver'
  820. },
  821. 'test/cpp/qps:qps_worker': {
  822. 'language': 'c++',
  823. 'build': 'test',
  824. 'run': False,
  825. '_TYPE': 'target',
  826. '_RENAME': 'qps_worker'
  827. },
  828. 'test/cpp/util:grpc_cli': {
  829. 'language': 'c++',
  830. 'build': 'test',
  831. 'run': False,
  832. '_TYPE': 'target',
  833. '_RENAME': 'grpc_cli'
  834. },
  835. # TODO(jtattermusch): create_jwt and verify_jwt breaks distribtests because it depends on grpc_test_utils and thus requires tests to be built
  836. # For now it's ok to disable them as these binaries aren't very useful anyway.
  837. #'test/core/security:create_jwt': { 'language': 'c', 'build': 'tool', '_TYPE': 'target', '_RENAME': 'grpc_create_jwt' },
  838. #'test/core/security:verify_jwt': { 'language': 'c', 'build': 'tool', '_TYPE': 'target', '_RENAME': 'grpc_verify_jwt' },
  839. # TODO(jtattermusch): add remaining tools such as grpc_print_google_default_creds_token (they are not used by bazel build)
  840. # TODO(jtattermusch): these fuzzers had no build.yaml equivalent
  841. # test/core/compression:message_compress_fuzzer
  842. # test/core/compression:message_decompress_fuzzer
  843. # test/core/compression:stream_compression_fuzzer
  844. # test/core/compression:stream_decompression_fuzzer
  845. # test/core/slice:b64_decode_fuzzer
  846. # test/core/slice:b64_encode_fuzzer
  847. }
  848. # We need a complete picture of all the targets and dependencies we're interested in
  849. # so we run multiple bazel queries and merge the results.
  850. _BAZEL_DEPS_QUERIES = [
  851. 'deps("//test/...")',
  852. 'deps("//:all")',
  853. 'deps("//src/compiler/...")',
  854. 'deps("//src/proto/...")',
  855. # The ^ is needed to differentiate proto_library from go_proto_library
  856. 'deps(kind("^proto_library", @envoy_api//envoy/...))',
  857. ]
  858. # Step 1: run a bunch of "bazel query --output xml" queries to collect
  859. # the raw build metadata from the bazel build.
  860. # At the end of this step we will have a dictionary of bazel rules
  861. # that are interesting to us (libraries, binaries, etc.) along
  862. # with their most important metadata (sources, headers, dependencies)
  863. #
  864. # Example of a single bazel rule after being populated:
  865. # '//:grpc' : { 'class': 'cc_library',
  866. # 'hdrs': ['//:include/grpc/byte_buffer.h', ... ],
  867. # 'srcs': ['//:src/core/lib/surface/init.cc', ... ],
  868. # 'deps': ['//:grpc_common', ...],
  869. # ... }
  870. bazel_rules = {}
  871. for query in _BAZEL_DEPS_QUERIES:
  872. bazel_rules.update(
  873. _extract_rules_from_bazel_xml(_bazel_query_xml_tree(query)))
  874. # Step 1.5: The sources for UPB protos are pre-generated, so we want
  875. # to expand the UPB proto library bazel rules into the generated
  876. # .upb.h and .upb.c files.
  877. _expand_upb_proto_library_rules(bazel_rules)
  878. # Step 2: Extract the known bazel cc_test tests. While most tests
  879. # will be buildable with other build systems just fine, some of these tests
  880. # would be too difficult to build and run with other build systems,
  881. # so we simply exclude the ones we don't want.
  882. # Note that while making tests buildable with other build systems
  883. # than just bazel is extra effort, we still need to do that for these
  884. # reasons:
  885. # - If our cmake build doesn't have any tests at all, it's hard to make
  886. # sure that what it built actually works (we need at least some "smoke tests").
  887. # This is quite important because the build flags between bazel / non-bazel flag might differ
  888. # (sometimes it's for interesting reasons that are not easy to overcome)
  889. # which makes it even more important to have at least some tests for cmake/make
  890. # - Our portability suite actually runs cmake tests and migration of portability
  891. # suite fully towards bazel might be intricate (e.g. it's unclear whether it's
  892. # possible to get a good enough coverage of different compilers / distros etc.
  893. # with bazel)
  894. # - some things that are considered "tests" in build.yaml-based builds are actually binaries
  895. # we'd want to be able to build anyway (qps_json_worker, interop_client, interop_server, grpc_cli)
  896. # so it's unclear how much make/cmake simplification we would gain by removing just some (but not all) test
  897. # TODO(jtattermusch): Investigate feasibility of running portability suite with bazel.
  898. tests = _exclude_unwanted_cc_tests(_extract_cc_tests(bazel_rules))
  899. # Step 3: Generate the "extra metadata" for all our build targets.
  900. # While the bazel rules give us most of the information we need,
  901. # the legacy "build.yaml" format requires some additional fields that
  902. # we cannot get just from bazel alone (we call that "extra metadata").
  903. # In this step, we basically analyze the build metadata we have from bazel
  904. # and use heuristics to determine (and sometimes guess) the right
  905. # extra metadata to use for each target.
  906. #
  907. # - For some targets (such as the public libraries, helper libraries
  908. # and executables) determining the right extra metadata is hard to do
  909. # automatically. For these targets, the extra metadata is supplied "manually"
  910. # in form of the _BUILD_EXTRA_METADATA dictionary. That allows us to match
  911. # the semantics of the legacy "build.yaml" as closely as possible.
  912. #
  913. # - For test binaries, it is possible to generate the "extra metadata" mostly
  914. # automatically using a rule-based heuristic approach because most tests
  915. # look and behave alike from the build's perspective.
  916. #
  917. # TODO(jtattermusch): Of course neither "_BUILD_EXTRA_METADATA" or
  918. # the heuristic approach used for tests are ideal and they cannot be made
  919. # to cover all possible situations (and are tailored to work with the way
  920. # the grpc build currently works), but the idea was to start with something
  921. # reasonably simple that matches the "build.yaml"-like semantics as closely
  922. # as possible (to avoid changing too many things at once) and gradually get
  923. # rid of the legacy "build.yaml"-specific fields one by one. Once that is done,
  924. # only very little "extra metadata" would be needed and/or it would be trivial
  925. # to generate it automatically.
  926. all_extra_metadata = {}
  927. all_extra_metadata.update(_BUILD_EXTRA_METADATA)
  928. all_extra_metadata.update(
  929. _generate_build_extra_metadata_for_tests(tests, bazel_rules))
  930. # Step 4: Compute the build metadata that will be used in the final build.yaml.
  931. # The final build metadata includes transitive dependencies, and sources/headers
  932. # expanded without intermediate dependencies.
  933. # Example:
  934. # '//:grpc' : { ...,
  935. # '_TRANSITIVE_DEPS': ['//:gpr_base', ...],
  936. # '_COLLAPSED_DEPS': ['gpr', ...],
  937. # '_COLLAPSED_SRCS': [...],
  938. # '_COLLAPSED_PUBLIC_HEADERS': [...],
  939. # '_COLLAPSED_HEADERS': [...]
  940. # }
  941. _populate_transitive_metadata(bazel_rules, list(all_extra_metadata.keys()))
  942. # Step 4a: Update the existing test metadata with the updated build metadata.
  943. # Certain build metadata of certain test targets depend on the transitive
  944. # metadata that wasn't available earlier.
  945. update_test_metadata_with_transitive_metadata(all_extra_metadata, bazel_rules)
  946. # Step 5: Generate the final metadata for all the targets.
  947. # This is done by combining the bazel build metadata and the "extra metadata"
  948. # we obtained in the previous step.
  949. # In this step, we also perform some interesting massaging of the target metadata
  950. # to end up with a result that is as similar to the legacy build.yaml data
  951. # as possible.
  952. # - Some targets get renamed (to match the legacy build.yaml target names)
  953. # - Some intermediate libraries get elided ("expanded") to better match the set
  954. # of targets provided by the legacy build.yaml build
  955. #
  956. # Originally the target renaming was introduced to address these concerns:
  957. # - avoid changing too many things at the same time and avoid people getting
  958. # confused by some well know targets suddenly being missing
  959. # - Makefile/cmake and also language-specific generators rely on some build
  960. # targets being called exactly the way they they are. Some of our testing
  961. # scrips also invoke executables (e.g. "qps_json_driver") by their name.
  962. # - The autogenerated test name from bazel includes the package path
  963. # (e.g. "test_cpp_TEST_NAME"). Without renaming, the target names would
  964. # end up pretty ugly (e.g. test_cpp_qps_qps_json_driver).
  965. # TODO(jtattermusch): reevaluate the need for target renaming in the future.
  966. #
  967. # Example of a single generated target:
  968. # 'grpc' : { 'language': 'c',
  969. # 'public_headers': ['include/grpc/byte_buffer.h', ... ],
  970. # 'headers': ['src/core/ext/filters/client_channel/client_channel.h', ... ],
  971. # 'src': ['src/core/lib/surface/init.cc', ... ],
  972. # 'deps': ['gpr', 'address_sorting', ...],
  973. # ... }
  974. all_targets_dict = _generate_build_metadata(all_extra_metadata, bazel_rules)
  975. # Step 6: convert the dictionary with all the targets to a dict that has
  976. # the desired "build.yaml"-like layout.
  977. # TODO(jtattermusch): We use the custom "build.yaml"-like layout because
  978. # currently all other build systems use that format as their source of truth.
  979. # In the future, we can get rid of this custom & legacy format entirely,
  980. # but we would need to update the generators for other build systems
  981. # at the same time.
  982. #
  983. # Layout of the result:
  984. # { 'libs': { TARGET_DICT_FOR_LIB_XYZ, ... },
  985. # 'targets': { TARGET_DICT_FOR_BIN_XYZ, ... },
  986. # 'tests': { TARGET_DICT_FOR_TEST_XYZ, ...} }
  987. build_yaml_like = _convert_to_build_yaml_like(all_targets_dict)
  988. # detect and report some suspicious situations we've seen before
  989. _detect_and_print_issues(build_yaml_like)
  990. # Step 7: Store the build_autogenerated.yaml in a deterministic (=sorted)
  991. # and cleaned-up form.
  992. # A basic overview of the resulting "build.yaml"-like format is here:
  993. # https://github.com/grpc/grpc/blob/master/templates/README.md
  994. # TODO(jtattermusch): The "cleanup" function is taken from the legacy
  995. # build system (which used build.yaml) and can be eventually removed.
  996. build_yaml_string = build_cleaner.cleaned_build_yaml_dict_as_string(
  997. build_yaml_like)
  998. with open('build_autogenerated.yaml', 'w') as file:
  999. file.write(build_yaml_string)