fix_build_deps.py
Go to the documentation of this file.
1 #!/usr/bin/env python3
2 
3 # Copyright 2022 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 import argparse
18 import collections
19 from doctest import SKIP
20 import os
21 import re
22 import subprocess
23 import sys
24 import tempfile
25 
26 # find our home
27 ROOT = os.path.abspath(os.path.join(os.path.dirname(sys.argv[0]), '../..'))
28 os.chdir(ROOT)
29 
30 vendors = collections.defaultdict(list)
31 scores = collections.defaultdict(int)
32 avoidness = collections.defaultdict(int)
33 consumes = {}
34 no_update = set()
35 buildozer_commands = []
36 needs_codegen_base_src = set()
37 original_deps = {}
38 original_external_deps = {}
39 
40 # TODO(ctiller): ideally we wouldn't hardcode a bunch of paths here.
41 # We can likely parse out BUILD files from dependencies to generate this index.
42 EXTERNAL_DEPS = {
43  'absl/base/attributes.h':
44  'absl/base:core_headers',
45  'absl/base/call_once.h':
46  'absl/base',
47  # TODO(ctiller) remove this
48  'absl/base/internal/endian.h':
49  'absl-base',
50  'absl/base/thread_annotations.h':
51  'absl/base:core_headers',
52  'absl/container/flat_hash_map.h':
53  'absl/container:flat_hash_map',
54  'absl/container/flat_hash_set.h':
55  'absl/container:flat_hash_set',
56  'absl/container/inlined_vector.h':
57  'absl/container:inlined_vector',
58  'absl/cleanup/cleanup.h':
59  'absl/cleanup',
60  'absl/functional/bind_front.h':
61  'absl/functional:bind_front',
62  'absl/functional/function_ref.h':
63  'absl/functional:function_ref',
64  'absl/hash/hash.h':
65  'absl/hash',
66  'absl/memory/memory.h':
67  'absl/memory',
68  'absl/meta/type_traits.h':
69  'absl/meta:type_traits',
70  'absl/random/random.h':
71  'absl/random',
72  'absl/status/status.h':
73  'absl/status',
74  'absl/status/statusor.h':
75  'absl/status:statusor',
76  'absl/strings/ascii.h':
77  'absl/strings',
78  'absl/strings/cord.h':
79  'absl/strings:cord',
80  'absl/strings/escaping.h':
81  'absl/strings',
82  'absl/strings/match.h':
83  'absl/strings',
84  'absl/strings/numbers.h':
85  'absl/strings',
86  'absl/strings/str_cat.h':
87  'absl/strings',
88  'absl/strings/str_format.h':
89  'absl/strings:str_format',
90  'absl/strings/str_join.h':
91  'absl/strings',
92  'absl/strings/str_replace.h':
93  'absl/strings',
94  'absl/strings/str_split.h':
95  'absl/strings',
96  'absl/strings/string_view.h':
97  'absl/strings',
98  'absl/strings/strip.h':
99  'absl/strings',
100  'absl/strings/substitute.h':
101  'absl/strings',
102  'absl/synchronization/mutex.h':
103  'absl/synchronization',
104  'absl/synchronization/notification.h':
105  'absl/synchronization',
106  'absl/time/clock.h':
107  'absl/time',
108  'absl/time/time.h':
109  'absl/time',
110  'absl/types/optional.h':
111  'absl/types:optional',
112  'absl/types/span.h':
113  'absl/types:span',
114  'absl/types/variant.h':
115  'absl/types:variant',
116  'absl/utility/utility.h':
117  'absl/utility',
118  'address_sorting/address_sorting.h':
119  'address_sorting',
120  'ares.h':
121  'cares',
122  'gmock/gmock.h':
123  'gtest',
124  'gtest/gtest.h':
125  'gtest',
126  'opencensus/trace/context_util.h':
127  'opencensus-trace-context_util',
128  'opencensus/trace/propagation/grpc_trace_bin.h':
129  'opencensus-trace-propagation',
130  'opencensus/tags/context_util.h':
131  'opencensus-tags-context_util',
132  'openssl/base.h':
133  'libssl',
134  'openssl/bio.h':
135  'libssl',
136  'openssl/bn.h':
137  'libcrypto',
138  'openssl/buffer.h':
139  'libcrypto',
140  'openssl/crypto.h':
141  'libcrypto',
142  'openssl/digest.h':
143  'libssl',
144  'openssl/engine.h':
145  'libcrypto',
146  'openssl/err.h':
147  'libcrypto',
148  'openssl/evp.h':
149  'libcrypto',
150  'openssl/hmac.h':
151  'libcrypto',
152  'openssl/pem.h':
153  'libcrypto',
154  'openssl/rsa.h':
155  'libcrypto',
156  'openssl/sha.h':
157  'libcrypto',
158  'openssl/ssl.h':
159  'libssl',
160  'openssl/tls1.h':
161  'libssl',
162  'openssl/x509.h':
163  'libcrypto',
164  'openssl/x509v3.h':
165  'libcrypto',
166  're2/re2.h':
167  're2',
168  'upb/def.h':
169  'upb_lib',
170  'upb/json_encode.h':
171  'upb_json_lib',
172  'upb/text_encode.h':
173  'upb_textformat_lib',
174  'upb/def.hpp':
175  'upb_reflection',
176  'upb/upb.h':
177  'upb_lib',
178  'upb/upb.hpp':
179  'upb_lib',
180  'xxhash.h':
181  'xxhash',
182  'zlib.h':
183  'madler_zlib',
184 }
185 
186 INTERNAL_DEPS = {
187  'google/api/expr/v1alpha1/syntax.upb.h':
188  'google_type_expr_upb',
189  'google/rpc/status.upb.h':
190  'google_rpc_status_upb',
191  'google/protobuf/any.upb.h':
192  'protobuf_any_upb',
193  'google/protobuf/duration.upb.h':
194  'protobuf_duration_upb',
195  'google/protobuf/struct.upb.h':
196  'protobuf_struct_upb',
197  'google/protobuf/timestamp.upb.h':
198  'protobuf_timestamp_upb',
199  'google/protobuf/wrappers.upb.h':
200  'protobuf_wrappers_upb',
201  'grpc/status.h':
202  'grpc_public_hdrs',
203  'src/proto/grpc/channelz/channelz.grpc.pb.h':
204  '//src/proto/grpc/channelz:channelz_proto',
205  'src/proto/grpc/core/stats.pb.h':
206  '//src/proto/grpc/core:stats_proto',
207  'src/proto/grpc/health/v1/health.upb.h':
208  'grpc_health_upb',
209  'src/proto/grpc/lb/v1/load_reporter.grpc.pb.h':
210  '//src/proto/grpc/lb/v1:load_reporter_proto',
211  'src/proto/grpc/lb/v1/load_balancer.upb.h':
212  'grpc_lb_upb',
213  'src/proto/grpc/reflection/v1alpha/reflection.grpc.pb.h':
214  '//src/proto/grpc/reflection/v1alpha:reflection_proto',
215  'src/proto/grpc/gcp/transport_security_common.upb.h':
216  'alts_upb',
217  'src/proto/grpc/gcp/altscontext.upb.h':
218  'alts_upb',
219  'src/proto/grpc/lookup/v1/rls.upb.h':
220  'rls_upb',
221  'src/proto/grpc/lookup/v1/rls_config.upb.h':
222  'rls_config_upb',
223  'src/proto/grpc/lookup/v1/rls_config.upbdefs.h':
224  'rls_config_upbdefs',
225  'src/proto/grpc/testing/xds/v3/csds.grpc.pb.h':
226  '//src/proto/grpc/testing/xds/v3:csds_proto',
227  'xds/data/orca/v3/orca_load_report.upb.h':
228  'xds_orca_upb',
229  'xds/service/orca/v3/orca.upb.h':
230  'xds_orca_service_upb',
231  'xds/type/v3/typed_struct.upb.h':
232  'xds_type_upb',
233 }
234 
235 
237 
238  def config_setting_group(self, **kwargs):
239  pass
240 
241 
243  hdrs=[],
244  public_hdrs=[],
245  srcs=[],
246  select_deps=None,
247  tags=[],
248  deps=[],
249  external_deps=[],
250  **kwargs):
251  if select_deps or 'nofixdeps' in tags or 'grpc-autodeps' not in tags:
252  no_update.add(name)
253  scores[name] = len(public_hdrs + hdrs)
254  # avoid_dep is the internal way of saying prefer something else
255  # we add grpc_avoid_dep to allow internal grpc-only stuff to avoid each
256  # other, whilst not biasing dependent projects
257  if 'avoid_dep' in tags or 'grpc_avoid_dep' in tags:
258  avoidness[name] += 10
259  if 'nofixdeps' in tags:
260  avoidness[name] += 1
261  for hdr in hdrs + public_hdrs:
262  vendors[hdr].append(name)
263  inc = set()
264  original_deps[name] = frozenset(deps)
265  original_external_deps[name] = frozenset(external_deps)
266  for src in hdrs + public_hdrs + srcs:
267  for line in open(src):
268  m = re.search(r'#include <(.*)>', line)
269  if m:
270  inc.add(m.group(1))
271  m = re.search(r'#include "(.*)"', line)
272  if m:
273  inc.add(m.group(1))
274  if 'grpc::g_glip' in line or 'grpc:g_core_codegen_interface' in line:
275  needs_codegen_base_src.add(name)
276  consumes[name] = list(inc)
277 
278 
279 def buildozer(cmd, target):
280  buildozer_commands.append('%s|%s' % (cmd, target))
281 
282 
283 def buildozer_set_list(name, values, target, via=""):
284  if not values:
285  buildozer('remove %s' % name, target)
286  return
287  adjust = via if via else name
288  buildozer('set %s %s' % (adjust, ' '.join('"%s"' % s for s in values)),
289  target)
290  if via:
291  buildozer('remove %s' % name, target)
292  buildozer('rename %s %s' % (via, name), target)
293 
294 
295 def score_edit_distance(proposed, existing):
296  """Score a proposed change primarily by edit distance"""
297  sum = 0
298  for p in proposed:
299  if p not in existing:
300  sum += 1
301  for e in existing:
302  if e not in proposed:
303  sum += 1
304  return sum
305 
306 
307 def total_score(proposal):
308  return sum(scores[dep] for dep in proposal)
309 
310 
311 def total_avoidness(proposal):
312  return sum(avoidness[dep] for dep in proposal)
313 
314 
315 def score_list_size(proposed, existing):
316  """Score a proposed change primarily by number of dependencies"""
317  return len(proposed)
318 
319 
320 def score_best(proposed, existing):
321  """Score a proposed change primarily by dependency score"""
322  return 0
323 
324 
325 SCORERS = {
326  'edit_distance': score_edit_distance,
327  'list_size': score_list_size,
328  'best': score_best,
329 }
330 
331 parser = argparse.ArgumentParser(description='Fix build dependencies')
332 parser.add_argument('targets',
333  nargs='*',
334  default=[],
335  help='targets to fix (empty => all)')
336 parser.add_argument('--score',
337  type=str,
338  default='edit_distance',
339  help='scoring function to use: one of ' +
340  ', '.join(SCORERS.keys()))
341 args = parser.parse_args()
342 
343 exec(
344  open('BUILD', 'r').read(), {
345  'load': lambda filename, *args: None,
346  'licenses': lambda licenses: None,
347  'package': lambda **kwargs: None,
348  'exports_files': lambda files: None,
349  'config_setting': lambda **kwargs: None,
350  'selects': FakeSelects(),
351  'python_config_settings': lambda **kwargs: None,
352  'grpc_cc_library': grpc_cc_library,
353  'select': lambda d: d["//conditions:default"],
354  'grpc_upb_proto_library': lambda name, **kwargs: None,
355  'grpc_upb_proto_reflection_library': lambda name, **kwargs: None,
356  'grpc_generate_one_off_targets': lambda: None,
357  'filegroup': lambda name, **kwargs: None,
358  }, {})
359 
360 
361 # Keeps track of all possible sets of dependencies that could satify the
362 # problem. (models the list monad in Haskell!)
363 class Choices:
364 
365  def __init__(self):
366  self.choices = set()
367  self.choices.add(frozenset())
368 
369  def add_one_of(self, choices):
370  if not choices:
371  return
372  new_choices = set()
373  for append_choice in choices:
374  for choice in self.choices:
375  new_choices.add(choice.union([append_choice]))
376  self.choices = new_choices
377 
378  def add(self, choice):
379  self.add_one_of([choice])
380 
381  def remove(self, remove):
382  new_choices = set()
383  for choice in self.choices:
384  new_choices.add(choice.difference([remove]))
385  self.choices = new_choices
386 
387  def best(self, scorer):
388  best = None
389  final_scorer = lambda x: (total_avoidness(x), scorer(x), total_score(x))
390  for choice in self.choices:
391  if best is None or final_scorer(choice) < final_scorer(best):
392  best = choice
393  return best
394 
395 
396 error = False
397 for library in sorted(consumes.keys()):
398  if library in no_update:
399  continue
400  if args.targets and library not in args.targets:
401  continue
402  hdrs = sorted(consumes[library])
403  deps = Choices()
404  external_deps = Choices()
405  for hdr in hdrs:
406  if hdr == 'src/core/lib/profiling/stap_probes.h':
407  continue
408 
409  if hdr in INTERNAL_DEPS:
410  deps.add(INTERNAL_DEPS[hdr])
411  continue
412 
413  if hdr in vendors:
414  deps.add_one_of(vendors[hdr])
415  continue
416 
417  if 'include/' + hdr in vendors:
418  deps.add_one_of(vendors['include/' + hdr])
419  continue
420 
421  if '.' not in hdr:
422  # assume a c++ system include
423  continue
424 
425  if hdr in EXTERNAL_DEPS:
426  external_deps.add(EXTERNAL_DEPS[hdr])
427  continue
428 
429  if hdr.startswith('opencensus/'):
430  trail = hdr[len('opencensus/'):]
431  trail = trail[:trail.find('/')]
432  external_deps.add('opencensus-' + trail)
433  continue
434 
435  if hdr.startswith('envoy/'):
436  path, file = os.path.split(hdr)
437  file = file.split('.')
438  path = path.split('/')
439  dep = '_'.join(path[:-1] + [file[1]])
440  deps.add(dep)
441  continue
442 
443  if hdr.startswith('google/protobuf/') and not hdr.endswith('.upb.h'):
444  external_deps.add('protobuf_headers')
445  continue
446 
447  if '/' not in hdr:
448  # assume a system include
449  continue
450 
451  is_sys_include = False
452  for sys_path in [
453  'sys',
454  'arpa',
455  'netinet',
456  'linux',
457  'android',
458  'mach',
459  'net',
460  'CoreFoundation',
461  ]:
462  if hdr.startswith(sys_path + '/'):
463  is_sys_include = True
464  break
465  if is_sys_include:
466  # assume a system include
467  continue
468 
469  print("# ERROR: can't categorize header: %s" % hdr)
470  error = True
471 
472  if library in needs_codegen_base_src:
473  deps.add('grpc++_codegen_base_src')
474 
475  deps.remove(library)
476 
477  deps = sorted(
478  deps.best(lambda x: SCORERS[args.score](x, original_deps[library])))
479  external_deps = sorted(
480  external_deps.best(lambda x: SCORERS[args.score]
481  (x, original_external_deps[library])))
482  target = ':' + library
483  buildozer_set_list('external_deps', external_deps, target, via='deps')
484  buildozer_set_list('deps', deps, target)
485 
486 if buildozer_commands:
487  ok_statuses = (0, 3)
488  temp = tempfile.NamedTemporaryFile()
489  open(temp.name, 'w').write('\n'.join(buildozer_commands))
490  c = ['tools/distrib/buildozer.sh', '-f', temp.name]
491  r = subprocess.call(c)
492  if r not in ok_statuses:
493  print('{} failed with status {}'.format(c, r))
494  sys.exit(1)
495 
496 if error:
497  sys.exit(1)
fix_build_deps.score_list_size
def score_list_size(proposed, existing)
Definition: fix_build_deps.py:315
fix_build_deps.buildozer_set_list
def buildozer_set_list(name, values, target, via="")
Definition: fix_build_deps.py:283
fix_build_deps.buildozer
def buildozer(cmd, target)
Definition: fix_build_deps.py:279
fix_build_deps.Choices.choices
choices
Definition: fix_build_deps.py:366
http2_test_server.format
format
Definition: http2_test_server.py:118
write
#define write
Definition: test-fs.c:47
grpc::testing::sum
double sum(const T &container, F functor)
Definition: test/cpp/qps/stats.h:30
fix_build_deps.Choices.__init__
def __init__(self)
Definition: fix_build_deps.py:365
fix_build_deps.Choices.add
def add(self, choice)
Definition: fix_build_deps.py:378
fix_build_deps.FakeSelects.config_setting_group
def config_setting_group(self, **kwargs)
Definition: fix_build_deps.py:238
fix_build_deps.Choices.remove
def remove(self, remove)
Definition: fix_build_deps.py:381
fix_build_deps.total_score
def total_score(proposal)
Definition: fix_build_deps.py:307
fix_build_deps.total_avoidness
def total_avoidness(proposal)
Definition: fix_build_deps.py:311
fix_build_deps.grpc_cc_library
def grpc_cc_library(name, hdrs=[], public_hdrs=[], srcs=[], select_deps=None, tags=[], deps=[], external_deps=[], **kwargs)
Definition: fix_build_deps.py:242
read
int read(izstream &zs, T *x, Items items)
Definition: bloaty/third_party/zlib/contrib/iostream2/zstream.h:115
fix_build_deps.FakeSelects
Definition: fix_build_deps.py:236
cpp.gmock_class.set
set
Definition: bloaty/third_party/googletest/googlemock/scripts/generator/cpp/gmock_class.py:44
open
#define open
Definition: test-fs.c:46
fix_build_deps.score_edit_distance
def score_edit_distance(proposed, existing)
Definition: fix_build_deps.py:295
fix_build_deps.Choices.best
def best(self, scorer)
Definition: fix_build_deps.py:387
fix_build_deps.Choices
Definition: fix_build_deps.py:363
len
int len
Definition: abseil-cpp/absl/base/internal/low_level_alloc_test.cc:46
fix_build_deps.score_best
def score_best(proposed, existing)
Definition: fix_build_deps.py:320
fix_build_deps.Choices.add_one_of
def add_one_of(self, choices)
Definition: fix_build_deps.py:369


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