gen_upb_api_from_bazel_xml.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 # Copyright 2021 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 
17 # This script generates upb source files (e.g. *.upb.c) from all upb targets
18 # in Bazel BUILD file. These generate upb files are for non-Bazel build such
19 # as makefile and python build which cannot generate them at the build time.
20 #
21 # As an example, for the following upb target
22 #
23 # grpc_upb_proto_library(
24 # name = "grpc_health_upb",
25 # deps = ["//src/proto/grpc/health/v1:health_proto_descriptor"],
26 # )
27 #
28 # this will generate these upb source files at src/core/ext/upb-generated.
29 #
30 # src/proto/grpc/health/v1/health.upb.c
31 # src/proto/grpc/health/v1/health.upb.h
32 
33 import argparse
34 import collections
35 import os
36 import shutil
37 import subprocess
38 import xml.etree.ElementTree
39 
40 # Rule object representing the UPB rule of Bazel BUILD.
41 Rule = collections.namedtuple('Rule', 'name type srcs deps proto_files')
42 
43 BAZEL_BIN = 'tools/bazel'
44 
45 
46 def parse_bazel_rule(elem):
47  '''Returns a rule from bazel XML rule.'''
48  srcs = []
49  deps = []
50  for child in elem:
51  if child.tag == 'list' and child.attrib['name'] == 'srcs':
52  for tag in child:
53  if tag.tag == 'label':
54  srcs.append(tag.attrib['value'])
55  if child.tag == 'list' and child.attrib['name'] == 'deps':
56  for tag in child:
57  if tag.tag == 'label':
58  deps.append(tag.attrib['value'])
59  return Rule(elem.attrib['name'], elem.attrib['class'], srcs, deps, [])
60 
61 
62 def get_transitive_protos(rules, t):
63  que = [
64  t,
65  ]
66  visited = set()
67  ret = []
68  while que:
69  name = que.pop(0)
70  rule = rules.get(name, None)
71  if rule:
72  for dep in rule.deps:
73  if dep not in visited:
74  visited.add(dep)
75  que.append(dep)
76  for src in rule.srcs:
77  if src.endswith('.proto'):
78  ret.append(src)
79  return list(set(ret))
80 
81 
83  '''Runs bazel query on given package file and returns all upb rules.'''
84  # Use a wrapper version of bazel in gRPC not to use system-wide bazel
85  # to avoid bazel conflict when running on Kokoro.
86  result = subprocess.check_output(
87  [BAZEL_BIN, 'query', '--output', 'xml', '--noimplicit_deps', '//:all'])
88  root = xml.etree.ElementTree.fromstring(result)
89  rules = [
90  parse_bazel_rule(elem)
91  for elem in root
92  if elem.tag == 'rule' and elem.attrib['class'] in [
93  'upb_proto_library',
94  'upb_proto_reflection_library',
95  ]
96  ]
97  # query all dependencies of upb rules to get a list of proto files
98  all_deps = [dep for rule in rules for dep in rule.deps]
99  result = subprocess.check_output([
100  BAZEL_BIN, 'query', '--output', 'xml', '--noimplicit_deps',
101  ' union '.join('deps({0})'.format(d) for d in all_deps)
102  ])
103  root = xml.etree.ElementTree.fromstring(result)
104  dep_rules = {}
105  for dep_rule in (
106  parse_bazel_rule(elem) for elem in root if elem.tag == 'rule'):
107  dep_rules[dep_rule.name] = dep_rule
108  # add proto files to upb rules transitively
109  for rule in rules:
110  if not rule.type.startswith('upb_proto_'):
111  continue
112  if len(rule.deps) == 1:
113  rule.proto_files.extend(
114  get_transitive_protos(dep_rules, rule.deps[0]))
115  return rules
116 
117 
119  result = subprocess.check_output([BAZEL_BIN, 'build'] +
120  [rule.name for rule in rules])
121 
122 
123 def get_upb_path(proto_path, ext):
124  return proto_path.replace(':', '/').replace('.proto', ext)
125 
126 
128  BAZEL_BIN_ROOT = 'bazel-bin/'
129  if elink[0].startswith('@'):
130  # external
131  return os.path.join(BAZEL_BIN_ROOT, 'external',
132  elink[0].replace('@', '').replace('//', ''))
133  else:
134  # internal
135  return BAZEL_BIN_ROOT
136 
137 
139  EXTERNAL_LINKS = [('@com_google_protobuf//', ':src/'),
140  ('@com_google_googleapis//', ''),
141  ('@com_github_cncf_udpa//', ''),
142  ('@com_envoyproxy_protoc_gen_validate//', ''),
143  ('@envoy_api//', ''), ('@opencensus_proto//', '')]
144  for external_link in EXTERNAL_LINKS:
145  if file.startswith(external_link[0]):
146  return external_link
147  return ('//', '')
148 
149 
150 def copy_upb_generated_files(rules, args):
151  files = {}
152  for rule in rules:
153  if rule.type == 'upb_proto_library':
154  frag = '.upb'
155  output_dir = args.upb_out
156  else:
157  frag = '.upbdefs'
158  output_dir = args.upbdefs_out
159  for proto_file in rule.proto_files:
160  elink = get_external_link(proto_file)
161  proto_file = proto_file[len(elink[0]) + len(elink[1]):]
162  for ext in ('.h', '.c'):
163  file = get_upb_path(proto_file, frag + ext)
164  src = os.path.join(get_bazel_bin_root_path(elink), file)
165  dst = os.path.join(output_dir, file)
166  files[src] = dst
167  for src, dst in files.items():
168  if args.verbose:
169  print('Copy:')
170  print(' {0}'.format(src))
171  print(' -> {0}'.format(dst))
172  os.makedirs(os.path.split(dst)[0], exist_ok=True)
173  shutil.copyfile(src, dst)
174 
175 
176 parser = argparse.ArgumentParser(description='UPB code-gen from bazel')
177 parser.add_argument('--verbose', default=False, action='store_true')
178 parser.add_argument('--upb_out',
179  default='src/core/ext/upb-generated',
180  help='Output directory for upb targets')
181 parser.add_argument('--upbdefs_out',
182  default='src/core/ext/upbdefs-generated',
183  help='Output directory for upbdefs targets')
184 
185 
186 def main():
187  args = parser.parse_args()
188  rules = read_upb_bazel_rules()
189  if args.verbose:
190  print('Rules:')
191  for rule in rules:
192  print(' name={0} type={1} proto_files={2}'.format(
193  rule.name, rule.type, rule.proto_files))
194  if rules:
195  build_upb_bazel_rules(rules)
196  copy_upb_generated_files(rules, args)
197 
198 
199 if __name__ == '__main__':
200  main()
http2_test_server.format
format
Definition: http2_test_server.py:118
gen_upb_api_from_bazel_xml.get_transitive_protos
def get_transitive_protos(rules, t)
Definition: gen_upb_api_from_bazel_xml.py:62
gen_upb_api_from_bazel_xml.parse_bazel_rule
def parse_bazel_rule(elem)
Definition: gen_upb_api_from_bazel_xml.py:46
gen_upb_api_from_bazel_xml.build_upb_bazel_rules
def build_upb_bazel_rules(rules)
Definition: gen_upb_api_from_bazel_xml.py:118
gen_upb_api_from_bazel_xml.get_upb_path
def get_upb_path(proto_path, ext)
Definition: gen_upb_api_from_bazel_xml.py:123
gen_upb_api_from_bazel_xml.get_bazel_bin_root_path
def get_bazel_bin_root_path(elink)
Definition: gen_upb_api_from_bazel_xml.py:127
main
Definition: main.py:1
gen_upb_api_from_bazel_xml.Rule
Rule
Definition: gen_upb_api_from_bazel_xml.py:41
cpp.gmock_class.set
set
Definition: bloaty/third_party/googletest/googlemock/scripts/generator/cpp/gmock_class.py:44
gen_upb_api_from_bazel_xml.read_upb_bazel_rules
def read_upb_bazel_rules()
Definition: gen_upb_api_from_bazel_xml.py:82
gen_upb_api_from_bazel_xml.main
def main()
Definition: gen_upb_api_from_bazel_xml.py:186
len
int len
Definition: abseil-cpp/absl/base/internal/low_level_alloc_test.cc:46
gen_upb_api_from_bazel_xml.get_external_link
def get_external_link(file)
Definition: gen_upb_api_from_bazel_xml.py:138
gen_upb_api_from_bazel_xml.copy_upb_generated_files
def copy_upb_generated_files(rules, args)
Definition: gen_upb_api_from_bazel_xml.py:150


grpc
Author(s):
autogenerated on Fri May 16 2025 02:58:25