@@ -50,24 +50,77 @@ CcSharedLibraryInfo = provider(
5050 },
5151)
5252
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+
5382# 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).
5585def _separate_static_and_dynamic_link_libraries (
5686 direct_children ,
57- can_be_linked_dynamically ):
87+ can_be_linked_dynamically ,
88+ transitive_dynamic_dep_labels = {}):
5889 node = None
59- all_children = list (direct_children )
90+ all_children = reversed (direct_children )
6091 targets_to_be_linked_statically_map = {}
6192 targets_to_be_linked_dynamically_set = {}
62-
6393 seen_labels = {}
6494
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+
65118 # Horrible I know. Perhaps Starlark team gives me a way to prune a tree.
66119 for i in range (2147483647 ):
67- if i == len (all_children ):
120+ if not len (all_children ):
68121 break
69122
70- node = all_children [i ]
123+ node = all_children [- 1 ]
71124
72125 must_add_children = False
73126
@@ -85,6 +138,8 @@ def _separate_static_and_dynamic_link_libraries(
85138 # the cc_shared_library implementation owners.
86139 has_owners_seen = False
87140 has_owners_not_seen = False
141+ linked_dynamically = False
142+ linked_statically = False
88143 for owner in node .owners :
89144 # TODO(bazel-team): Do not convert Labels to string to save on
90145 # garbage string allocations.
@@ -99,18 +154,62 @@ def _separate_static_and_dynamic_link_libraries(
99154
100155 if owner_str in can_be_linked_dynamically :
101156 targets_to_be_linked_dynamically_set [owner_str ] = True
157+ linked_dynamically = True
102158 else :
103159 targets_to_be_linked_statically_map [owner_str ] = node .linkable_more_than_once
104160 must_add_children = True
161+ linked_statically = True
105162
106163 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+ )
109177
110178 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 )
114213
115214def _create_linker_context (ctx , linker_inputs ):
116215 return cc_common .create_linking_context (
@@ -129,7 +228,7 @@ def _merge_cc_shared_library_infos(ctx):
129228 dynamic_deps .append (dynamic_dep_entry )
130229 transitive_dynamic_deps .append (dep [CcSharedLibraryInfo ].dynamic_deps )
131230
132- return depset (direct = dynamic_deps , transitive = transitive_dynamic_deps )
231+ return depset (direct = dynamic_deps , transitive = transitive_dynamic_deps , order = "topological" )
133232
134233def _build_exports_map_from_only_dynamic_deps (merged_shared_library_infos ):
135234 exports_map = {}
@@ -267,7 +366,6 @@ def _filter_inputs(
267366 deps ,
268367 transitive_exports ,
269368 link_once_static_libs_map ):
270- linker_inputs = []
271369 curr_link_once_static_libs_set = {}
272370
273371 graph_structure_aspect_nodes = []
@@ -291,7 +389,11 @@ def _filter_inputs(
291389
292390 # The targets_to_be_linked_statically_map points to whether the target to
293391 # 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 (
295397 graph_structure_aspect_nodes ,
296398 can_be_linked_dynamically ,
297399 )
@@ -313,6 +415,11 @@ def _filter_inputs(
313415 precompiled_only_dynamic_libraries = []
314416 exports = {}
315417 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 )
316423
317424 # We use this dictionary to give an error if a target containing only
318425 # precompiled dynamic libraries is placed directly in roots. If such a
@@ -331,7 +438,8 @@ def _filter_inputs(
331438 # Link the library in this iteration dynamically,
332439 # transitive_exports contains the artifacts produced by a
333440 # 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
335443 elif owner in targets_to_be_linked_statically_map :
336444 if owner in link_once_static_libs_map :
337445 # We are building a dictionary that will allow us to give
@@ -357,7 +465,8 @@ def _filter_inputs(
357465 if not len (static_libraries ):
358466 if owner in direct_deps_set :
359467 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
361470 continue
362471 if len (static_libraries ) and owner in dynamic_only_roots :
363472 dynamic_only_roots .pop (owner )
@@ -377,7 +486,8 @@ def _filter_inputs(
377486 ):
378487 exports [owner ] = True
379488
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
381491
382492 if not targets_to_be_linked_statically_map [owner ]:
383493 curr_link_once_static_libs_set [owner ] = True
@@ -390,8 +500,14 @@ def _filter_inputs(
390500 message += dynamic_only_root + "\n "
391501 fail (message )
392502
503+ sorted_linker_inputs = _sort_linker_inputs (
504+ topologically_sorted_labels ,
505+ label_to_linker_inputs ,
506+ linker_inputs_count ,
507+ )
508+
393509 _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 )
395511
396512def _throw_linked_but_not_exported_errors (error_libs_dict ):
397513 if not error_libs_dict :
@@ -684,3 +800,4 @@ build_link_once_static_libs_map = _build_link_once_static_libs_map
684800build_exports_map_from_only_dynamic_deps = _build_exports_map_from_only_dynamic_deps
685801throw_linked_but_not_exported_errors = _throw_linked_but_not_exported_errors
686802separate_static_and_dynamic_link_libraries = _separate_static_and_dynamic_link_libraries
803+ sort_linker_inputs = _sort_linker_inputs
0 commit comments