@@ -50,24 +50,77 @@ CcSharedLibraryInfo = provider(
50
50
},
51
51
)
52
52
53
+ def _programmatic_error (message = "" ):
54
+ fail ("Your build has triggered a programmatic error in the cc_shared_library rule. " +
55
+ "Please file an issue in https://github.com/bazelbuild/bazel : {}" .format (message ))
56
+
57
+ def _sort_linker_inputs (topologically_sorted_labels , label_to_linker_inputs , linker_inputs_count ):
58
+ # len(label_to_linker_inputs) might not match the topologically_sorted_labels
59
+ # size. The latter is looking at nodes in the graph but a node may not
60
+ # necessarily contribute any linker_inputs. For example a cc_library with
61
+ # no sources and only deps. Every linker_input.owner must be in
62
+ # topologically_sorted_labels otherwise there is an error in the rule
63
+ # implementation of the target providing that linker_input, e.g. it's
64
+ # missing a CcSharedLibraryHintInfo if it had custom owner names for linker
65
+ # inputs.
66
+ sorted_linker_inputs = []
67
+ for label in topologically_sorted_labels :
68
+ if label not in label_to_linker_inputs :
69
+ # This is ok. It can happen if no linker_inputs
70
+ # were added by a node in the graph.
71
+ continue
72
+ sorted_linker_inputs .extend (label_to_linker_inputs [label ])
73
+
74
+ if len (sorted_linker_inputs ) != linker_inputs_count :
75
+ owners = []
76
+ for sorted_linker_input in sorted_linker_inputs :
77
+ owners .append (str (sorted_linker_input .owner ))
78
+ _programmatic_error ("{} vs {}" .format ("," .join (owners ), linker_inputs_count ))
79
+
80
+ return sorted_linker_inputs
81
+
53
82
# For each target, find out whether it should be linked statically or
54
- # dynamically.
83
+ # dynamically. The transitive_dynamic_dep_labels parameter is only needed for
84
+ # binaries because they link all dynamic_deps (cc_binary|cc_test).
55
85
def _separate_static_and_dynamic_link_libraries (
56
86
direct_children ,
57
- can_be_linked_dynamically ):
87
+ can_be_linked_dynamically ,
88
+ transitive_dynamic_dep_labels = {}):
58
89
node = None
59
- all_children = list (direct_children )
90
+ all_children = reversed (direct_children )
60
91
targets_to_be_linked_statically_map = {}
61
92
targets_to_be_linked_dynamically_set = {}
62
-
63
93
seen_labels = {}
64
94
95
+ # The cc_shared_library graph is parallel to the cc_library graph.
96
+ # Propagation of linker inputs between cc_libraries happens via the CcInfo
97
+ # provider. Parallel to this we have cc_shared_libraries which may decide
98
+ # different partitions of the cc_library graph.
99
+ #
100
+ # In a previous implementation of cc_shared_library we relied on the
101
+ # topological sort given by flattening
102
+ # cc_info.linking_context.linker_inputs.to_list(), however this was wrong
103
+ # because the dependencies of a shared library (i.e. a pruned node here)
104
+ # influenced the final order.
105
+ #
106
+ # In order to fix this, the pruning below was changed from breadth-first
107
+ # traversal to depth-first traversal. While doing this we also recreate a
108
+ # depset with topological order that takes into account the pruned nodes
109
+ # and which will later be used to order the libraries in the linking
110
+ # command line. This will be in topological order and will respect the
111
+ # order of the deps as listed on the BUILD file as much as possible.
112
+ #
113
+ # Here we say "first_owner" because each node (see GraphNodeInfo) may have
114
+ # more than one linker_input (each potentially with a different owner) but
115
+ # using only the first owner as a key is enough.
116
+ first_owner_to_depset = {}
117
+
65
118
# Horrible I know. Perhaps Starlark team gives me a way to prune a tree.
66
119
for i in range (2147483647 ):
67
- if i == len (all_children ):
120
+ if not len (all_children ):
68
121
break
69
122
70
- node = all_children [i ]
123
+ node = all_children [- 1 ]
71
124
72
125
must_add_children = False
73
126
@@ -85,6 +138,8 @@ def _separate_static_and_dynamic_link_libraries(
85
138
# the cc_shared_library implementation owners.
86
139
has_owners_seen = False
87
140
has_owners_not_seen = False
141
+ linked_dynamically = False
142
+ linked_statically = False
88
143
for owner in node .owners :
89
144
# TODO(bazel-team): Do not convert Labels to string to save on
90
145
# garbage string allocations.
@@ -99,18 +154,62 @@ def _separate_static_and_dynamic_link_libraries(
99
154
100
155
if owner_str in can_be_linked_dynamically :
101
156
targets_to_be_linked_dynamically_set [owner_str ] = True
157
+ linked_dynamically = True
102
158
else :
103
159
targets_to_be_linked_statically_map [owner_str ] = node .linkable_more_than_once
104
160
must_add_children = True
161
+ linked_statically = True
105
162
106
163
if has_owners_seen and has_owners_not_seen :
107
- fail ("Your build has triggered a programmatic error in the cc_shared_library rule. " +
108
- "Please file an issue in https://github.com/bazelbuild/bazel" )
164
+ _programmatic_error ()
165
+
166
+ if linked_dynamically and linked_statically :
167
+ error_owners_list = [str (owner ) for owner in node .owners ]
168
+
169
+ # Our granularity is target level. Unless there is a different
170
+ # unsupported custom implementation of this rule it should be
171
+ # impossible for two linker_inputs from the same target to be
172
+ # linked differently, one statically and the other dynamically.
173
+ _programmatic_error (
174
+ message = "Nodes with linker_inputs linked statically and dynamically:" +
175
+ "\n {}" .format ("\n " .join (error_owners_list )),
176
+ )
109
177
110
178
if must_add_children :
111
- all_children .extend (node .children )
112
-
113
- return (targets_to_be_linked_statically_map , targets_to_be_linked_dynamically_set )
179
+ # The order in which we process the children matter. all_children
180
+ # is being used as a stack, we will process first the nodes at the
181
+ # top of the stack (last in the list). The children are the
182
+ # dependencies of the current node, in order to respect the order
183
+ # in which dependencies were listed in the deps attribute in the
184
+ # BUILD file we must reverse the list so that the first one listed
185
+ # in the BUILD file is processed first.
186
+ all_children .extend (reversed (node .children ))
187
+ else :
188
+ if node .owners [0 ] not in first_owner_to_depset :
189
+ # We have 3 cases in this branch:
190
+ # 1. Node has no children
191
+ # 2. The children have been pruned because the node is linked dynamically
192
+ # 3. Node has children that have been processed
193
+ # For case 3 we add the children's depsets. For case 2 we add the dynamic
194
+ # dep labels for transitive dynamic deps.
195
+ transitive = []
196
+ if str (node .owners [0 ]) in targets_to_be_linked_statically_map :
197
+ for child in node .children :
198
+ transitive .append (first_owner_to_depset [child .owners [0 ]])
199
+ elif str (node .owners [0 ]) in transitive_dynamic_dep_labels :
200
+ transitive .append (transitive_dynamic_dep_labels [str (node .owners [0 ])])
201
+
202
+ first_owner_to_depset [node .owners [0 ]] = depset (direct = node .owners , transitive = transitive , order = "topological" )
203
+ all_children .pop ()
204
+
205
+ topologically_sorted_labels = []
206
+ if direct_children :
207
+ transitive = []
208
+ for child in direct_children :
209
+ transitive .append (first_owner_to_depset [child .owners [0 ]])
210
+ topologically_sorted_labels = depset (transitive = transitive , order = "topological" ).to_list ()
211
+
212
+ return (targets_to_be_linked_statically_map , targets_to_be_linked_dynamically_set , topologically_sorted_labels )
114
213
115
214
def _create_linker_context (ctx , linker_inputs ):
116
215
return cc_common .create_linking_context (
@@ -129,7 +228,7 @@ def _merge_cc_shared_library_infos(ctx):
129
228
dynamic_deps .append (dynamic_dep_entry )
130
229
transitive_dynamic_deps .append (dep [CcSharedLibraryInfo ].dynamic_deps )
131
230
132
- return depset (direct = dynamic_deps , transitive = transitive_dynamic_deps )
231
+ return depset (direct = dynamic_deps , transitive = transitive_dynamic_deps , order = "topological" )
133
232
134
233
def _build_exports_map_from_only_dynamic_deps (merged_shared_library_infos ):
135
234
exports_map = {}
@@ -267,7 +366,6 @@ def _filter_inputs(
267
366
deps ,
268
367
transitive_exports ,
269
368
link_once_static_libs_map ):
270
- linker_inputs = []
271
369
curr_link_once_static_libs_set = {}
272
370
273
371
graph_structure_aspect_nodes = []
@@ -291,7 +389,11 @@ def _filter_inputs(
291
389
292
390
# The targets_to_be_linked_statically_map points to whether the target to
293
391
# be linked statically can be linked more than once.
294
- (targets_to_be_linked_statically_map , targets_to_be_linked_dynamically_set ) = _separate_static_and_dynamic_link_libraries (
392
+ (
393
+ targets_to_be_linked_statically_map ,
394
+ targets_to_be_linked_dynamically_set ,
395
+ topologically_sorted_labels ,
396
+ ) = _separate_static_and_dynamic_link_libraries (
295
397
graph_structure_aspect_nodes ,
296
398
can_be_linked_dynamically ,
297
399
)
@@ -313,6 +415,11 @@ def _filter_inputs(
313
415
precompiled_only_dynamic_libraries = []
314
416
exports = {}
315
417
linker_inputs_seen = {}
418
+ linker_inputs_count = 0
419
+ label_to_linker_inputs = {}
420
+
421
+ def _add_linker_input_to_dict (owner , linker_input ):
422
+ label_to_linker_inputs .setdefault (owner , []).append (linker_input )
316
423
317
424
# We use this dictionary to give an error if a target containing only
318
425
# precompiled dynamic libraries is placed directly in roots. If such a
@@ -331,7 +438,8 @@ def _filter_inputs(
331
438
# Link the library in this iteration dynamically,
332
439
# transitive_exports contains the artifacts produced by a
333
440
# cc_shared_library
334
- linker_inputs .append (transitive_exports [owner ])
441
+ _add_linker_input_to_dict (linker_input .owner , transitive_exports [owner ])
442
+ linker_inputs_count += 1
335
443
elif owner in targets_to_be_linked_statically_map :
336
444
if owner in link_once_static_libs_map :
337
445
# We are building a dictionary that will allow us to give
@@ -357,7 +465,8 @@ def _filter_inputs(
357
465
if not len (static_libraries ):
358
466
if owner in direct_deps_set :
359
467
dynamic_only_roots [owner ] = True
360
- linker_inputs .append (linker_input )
468
+ _add_linker_input_to_dict (linker_input .owner , linker_input )
469
+ linker_inputs_count += 1
361
470
continue
362
471
if len (static_libraries ) and owner in dynamic_only_roots :
363
472
dynamic_only_roots .pop (owner )
@@ -377,7 +486,8 @@ def _filter_inputs(
377
486
):
378
487
exports [owner ] = True
379
488
380
- linker_inputs .append (linker_input_to_be_linked_statically )
489
+ _add_linker_input_to_dict (linker_input .owner , linker_input_to_be_linked_statically )
490
+ linker_inputs_count += 1
381
491
382
492
if not targets_to_be_linked_statically_map [owner ]:
383
493
curr_link_once_static_libs_set [owner ] = True
@@ -390,8 +500,14 @@ def _filter_inputs(
390
500
message += dynamic_only_root + "\n "
391
501
fail (message )
392
502
503
+ sorted_linker_inputs = _sort_linker_inputs (
504
+ topologically_sorted_labels ,
505
+ label_to_linker_inputs ,
506
+ linker_inputs_count ,
507
+ )
508
+
393
509
_throw_linked_but_not_exported_errors (linked_statically_but_not_exported )
394
- return (exports , linker_inputs , curr_link_once_static_libs_set .keys (), precompiled_only_dynamic_libraries )
510
+ return (exports , sorted_linker_inputs , curr_link_once_static_libs_set .keys (), precompiled_only_dynamic_libraries )
395
511
396
512
def _throw_linked_but_not_exported_errors (error_libs_dict ):
397
513
if not error_libs_dict :
@@ -684,3 +800,4 @@ build_link_once_static_libs_map = _build_link_once_static_libs_map
684
800
build_exports_map_from_only_dynamic_deps = _build_exports_map_from_only_dynamic_deps
685
801
throw_linked_but_not_exported_errors = _throw_linked_but_not_exported_errors
686
802
separate_static_and_dynamic_link_libraries = _separate_static_and_dynamic_link_libraries
803
+ sort_linker_inputs = _sort_linker_inputs
0 commit comments