xref: /aosp_15_r20/external/bazelbuild-rules_cc/tools/migration/ctoolchain_comparator_lib.py (revision eed53cd41c5909d05eedc7ad9720bb158fd93452)
1# Copyright 2018 The Bazel Authors. All rights reserved.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7#    http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14"""Module providing compare_ctoolchains function.
15
16compare_ctoolchains takes in two parsed CToolchains and compares them
17"""
18
19
20def _print_difference(field_name, before_value, after_value):
21  if not before_value and after_value:
22    print(("Difference in '%s' field:\nValue before change is not set\n"
23           "Value after change is set to '%s'") % (field_name, after_value))
24  elif before_value and not after_value:
25    print(("Difference in '%s' field:\nValue before change is set to '%s'\n"
26           "Value after change is not set") % (field_name, before_value))
27  else:
28    print(("Difference in '%s' field:\nValue before change:\t'%s'\n"
29           "Value after change:\t'%s'\n") % (field_name, before_value,
30                                             after_value))
31
32
33def _array_to_string(arr, ordered=False):
34  if not arr:
35    return "[]"
36  elif len(arr) == 1:
37    return "[" + list(arr)[0] + "]"
38  if not ordered:
39    return "[\n\t%s\n]" % "\n\t".join(arr)
40  else:
41    return "[\n\t%s\n]" % "\n\t".join(sorted(list(arr)))
42
43
44def _check_with_feature_set_equivalence(before, after):
45  before_set = set()
46  after_set = set()
47  for el in before:
48    before_set.add((str(set(el.feature)), str(set(el.not_feature))))
49  for el in after:
50    after_set.add((str(set(el.feature)), str(set(el.not_feature))))
51  return before_set == after_set
52
53
54def _check_tool_equivalence(before, after):
55  """Compares two "CToolchain.Tool"s."""
56  if before.tool_path == "NOT_USED":
57    before.tool_path = ""
58  if after.tool_path == "NOT_USED":
59    after.tool_path = ""
60  if before.tool_path != after.tool_path:
61    return False
62  if set(before.execution_requirement) != set(after.execution_requirement):
63    return False
64  if not _check_with_feature_set_equivalence(before.with_feature,
65                                             after.with_feature):
66    return False
67  return True
68
69
70def _check_flag_group_equivalence(before, after):
71  """Compares two "CToolchain.FlagGroup"s."""
72  if before.flag != after.flag:
73    return False
74  if before.expand_if_true != after.expand_if_true:
75    return False
76  if before.expand_if_false != after.expand_if_false:
77    return False
78  if set(before.expand_if_all_available) != set(after.expand_if_all_available):
79    return False
80  if set(before.expand_if_none_available) != set(
81      after.expand_if_none_available):
82    return False
83  if before.iterate_over != after.iterate_over:
84    return False
85  if before.expand_if_equal != after.expand_if_equal:
86    return False
87  if len(before.flag_group) != len(after.flag_group):
88    return False
89  for (flag_group_before, flag_group_after) in zip(before.flag_group,
90                                                   after.flag_group):
91    if not _check_flag_group_equivalence(flag_group_before, flag_group_after):
92      return False
93  return True
94
95
96def _check_flag_set_equivalence(before, after, in_action_config=False):
97  """Compares two "CToolchain.FlagSet"s."""
98  # ActionConfigs in proto format do not have a 'FlagSet.action' field set.
99  # Instead, when construction the Java ActionConfig object, we set the
100  # flag_set.action field to the action name. This currently causes the
101  # CcToolchainConfigInfo.proto to generate a CToolchain.ActionConfig that still
102  # has the action name in the FlagSet.action field, therefore we don't compare
103  # the FlagSet.action field when comparing flag_sets that belong to an
104  # ActionConfig.
105  if not in_action_config and set(before.action) != set(after.action):
106    return False
107  if not _check_with_feature_set_equivalence(before.with_feature,
108                                             after.with_feature):
109    return False
110  if len(before.flag_group) != len(after.flag_group):
111    return False
112  for (flag_group_before, flag_group_after) in zip(before.flag_group,
113                                                   after.flag_group):
114    if not _check_flag_group_equivalence(flag_group_before, flag_group_after):
115      return False
116  return True
117
118
119def _check_action_config_equivalence(before, after):
120  """Compares two "CToolchain.ActionConfig"s."""
121  if before.config_name != after.config_name:
122    return False
123  if before.action_name != after.action_name:
124    return False
125  if before.enabled != after.enabled:
126    return False
127  if len(before.tool) != len(after.tool):
128    return False
129  for (tool_before, tool_after) in zip(before.tool, after.tool):
130    if not _check_tool_equivalence(tool_before, tool_after):
131      return False
132  if before.implies != after.implies:
133    return False
134  if len(before.flag_set) != len(after.flag_set):
135    return False
136  for (flag_set_before, flag_set_after) in zip(before.flag_set, after.flag_set):
137    if not _check_flag_set_equivalence(flag_set_before, flag_set_after, True):
138      return False
139  return True
140
141
142def _check_env_set_equivalence(before, after):
143  """Compares two "CToolchain.EnvSet"s."""
144  if set(before.action) != set(after.action):
145    return False
146  if not _check_with_feature_set_equivalence(before.with_feature,
147                                             after.with_feature):
148    return False
149  if before.env_entry != after.env_entry:
150    return False
151  return True
152
153
154def _check_feature_equivalence(before, after):
155  """Compares two "CToolchain.Feature"s."""
156  if before.name != after.name:
157    return False
158  if before.enabled != after.enabled:
159    return False
160  if len(before.flag_set) != len(after.flag_set):
161    return False
162  for (flag_set_before, flag_set_after) in zip(before.flag_set, after.flag_set):
163    if not _check_flag_set_equivalence(flag_set_before, flag_set_after):
164      return False
165  if len(before.env_set) != len(after.env_set):
166    return False
167  for (env_set_before, env_set_after) in zip(before.env_set, after.env_set):
168    if not _check_env_set_equivalence(env_set_before, env_set_after):
169      return False
170  if len(before.requires) != len(after.requires):
171    return False
172  for (requires_before, requires_after) in zip(before.requires, after.requires):
173    if set(requires_before.feature) != set(requires_after.feature):
174      return False
175  if before.implies != after.implies:
176    return False
177  if before.provides != after.provides:
178    return False
179  return True
180
181
182def _compare_features(features_before, features_after):
183  """Compares two "CToolchain.FlagFeature" lists."""
184  feature_name_to_feature_before = {}
185  feature_name_to_feature_after = {}
186  for feature in features_before:
187    feature_name_to_feature_before[feature.name] = feature
188  for feature in features_after:
189    feature_name_to_feature_after[feature.name] = feature
190
191  feature_names_before = set(feature_name_to_feature_before.keys())
192  feature_names_after = set(feature_name_to_feature_after.keys())
193
194  before_after_diff = feature_names_before - feature_names_after
195  after_before_diff = feature_names_after - feature_names_before
196
197  diff_string = "Difference in 'feature' field:"
198  found_difference = False
199  if before_after_diff:
200    if not found_difference:
201      print(diff_string)  # pylint: disable=superfluous-parens
202      found_difference = True
203    print(("* List before change contains entries for the following features "
204           "that the list after the change doesn't:\n%s") % _array_to_string(
205               before_after_diff, ordered=True))
206  if after_before_diff:
207    if not found_difference:
208      print(diff_string)  # pylint: disable=superfluous-parens
209      found_difference = True
210    print(("* List after change contains entries for the following features "
211           "that the list before the change doesn't:\n%s") % _array_to_string(
212               after_before_diff, ordered=True))
213
214  names_before = [feature.name for feature in features_before]
215  names_after = [feature.name for feature in features_after]
216  if names_before != names_after:
217    if not found_difference:
218      print(diff_string)  # pylint: disable=superfluous-parens
219      found_difference = True
220    print(("Features not in right order:\n"
221           "* List of features before change:\t%s"
222           "* List of features before change:\t%s") %
223          (_array_to_string(names_before), _array_to_string(names_after)))
224  for name in feature_name_to_feature_before:
225    feature_before = feature_name_to_feature_before[name]
226    feature_after = feature_name_to_feature_after.get(name, None)
227    if feature_after and not _check_feature_equivalence(feature_before,
228                                                        feature_after):
229      if not found_difference:
230        print(diff_string)  # pylint: disable=superfluous-parens
231        found_difference = True
232      print(("* Feature '%s' differs before and after the change:\n"
233             "Value before change:\n%s\n"
234             "Value after change:\n%s") % (name, str(feature_before),
235                                           str(feature_after)))
236  if found_difference:
237    print("")  # pylint: disable=superfluous-parens
238  return found_difference
239
240
241def _compare_action_configs(action_configs_before, action_configs_after):
242  """Compares two "CToolchain.ActionConfig" lists."""
243  action_name_to_action_before = {}
244  action_name_to_action_after = {}
245  for action_config in action_configs_before:
246    action_name_to_action_before[action_config.config_name] = action_config
247  for action_config in action_configs_after:
248    action_name_to_action_after[action_config.config_name] = action_config
249
250  config_names_before = set(action_name_to_action_before.keys())
251  config_names_after = set(action_name_to_action_after.keys())
252
253  before_after_diff = config_names_before - config_names_after
254  after_before_diff = config_names_after - config_names_before
255
256  diff_string = "Difference in 'action_config' field:"
257  found_difference = False
258  if before_after_diff:
259    if not found_difference:
260      print(diff_string)  # pylint: disable=superfluous-parens
261      found_difference = True
262    print(("* List before change contains entries for the following "
263           "action_configs that the list after the change doesn't:\n%s") %
264          _array_to_string(before_after_diff, ordered=True))
265  if after_before_diff:
266    if not found_difference:
267      print(diff_string)  # pylint: disable=superfluous-parens
268      found_difference = True
269    print(("* List after change contains entries for the following "
270           "action_configs that the list before the change doesn't:\n%s") %
271          _array_to_string(after_before_diff, ordered=True))
272
273  names_before = [config.config_name for config in action_configs_before]
274  names_after = [config.config_name for config in action_configs_after]
275  if names_before != names_after:
276    if not found_difference:
277      print(diff_string)  # pylint: disable=superfluous-parens
278      found_difference = True
279    print(("Action configs not in right order:\n"
280           "* List of action configs before change:\t%s"
281           "* List of action_configs before change:\t%s") %
282          (_array_to_string(names_before), _array_to_string(names_after)))
283  for name in config_names_before:
284    action_config_before = action_name_to_action_before[name]
285    action_config_after = action_name_to_action_after.get(name, None)
286    if action_config_after and not _check_action_config_equivalence(
287        action_config_before, action_config_after):
288      if not found_difference:
289        print(diff_string)  # pylint: disable=superfluous-parens
290        found_difference = True
291      print(("* Action config '%s' differs before and after the change:\n"
292             "Value before change:\n%s\n"
293             "Value after change:\n%s") % (name, str(action_config_before),
294                                           str(action_config_after)))
295  if found_difference:
296    print("")  # pylint: disable=superfluous-parens
297  return found_difference
298
299
300def _compare_tool_paths(tool_paths_before, tool_paths_after):
301  """Compares two "CToolchain.ToolPath" lists."""
302  tool_to_path_before = {}
303  tool_to_path_after = {}
304  for tool_path in tool_paths_before:
305    tool_to_path_before[tool_path.name] = (
306        tool_path.path if tool_path.path != "NOT_USED" else "")
307  for tool_path in tool_paths_after:
308    tool_to_path_after[tool_path.name] = (
309        tool_path.path if tool_path.path != "NOT_USED" else "")
310
311  tool_names_before = set(tool_to_path_before.keys())
312  tool_names_after = set(tool_to_path_after.keys())
313
314  before_after_diff = tool_names_before - tool_names_after
315  after_before_diff = tool_names_after - tool_names_before
316
317  diff_string = "Difference in 'tool_path' field:"
318  found_difference = False
319  if before_after_diff:
320    if not found_difference:
321      print(diff_string)  # pylint: disable=superfluous-parens
322      found_difference = True
323    print(("* List before change contains entries for the following tools "
324           "that the list after the change doesn't:\n%s") % _array_to_string(
325               before_after_diff, ordered=True))
326  if after_before_diff:
327    if not found_difference:
328      print(diff_string)  # pylint: disable=superfluous-parens
329      found_difference = True
330    print(("* List after change contains entries for the following tools that "
331           "the list before the change doesn't:\n%s") % _array_to_string(
332               after_before_diff, ordered=True))
333
334  for tool in tool_to_path_before:
335    path_before = tool_to_path_before[tool]
336    path_after = tool_to_path_after.get(tool, None)
337    if path_after and path_after != path_before:
338      if not found_difference:
339        print(diff_string)  # pylint: disable=superfluous-parens
340        found_difference = True
341      print(("* Path for tool '%s' differs before and after the change:\n"
342             "Value before change:\t'%s'\n"
343             "Value after change:\t'%s'") % (tool, path_before, path_after))
344  if found_difference:
345    print("")  # pylint: disable=superfluous-parens
346  return found_difference
347
348
349def _compare_make_variables(make_variables_before, make_variables_after):
350  """Compares two "CToolchain.MakeVariable" lists."""
351  name_to_variable_before = {}
352  name_to_variable_after = {}
353  for variable in make_variables_before:
354    name_to_variable_before[variable.name] = variable.value
355  for variable in make_variables_after:
356    name_to_variable_after[variable.name] = variable.value
357
358  variable_names_before = set(name_to_variable_before.keys())
359  variable_names_after = set(name_to_variable_after.keys())
360
361  before_after_diff = variable_names_before - variable_names_after
362  after_before_diff = variable_names_after - variable_names_before
363
364  diff_string = "Difference in 'make_variable' field:"
365  found_difference = False
366  if before_after_diff:
367    if not found_difference:
368      print(diff_string)  # pylint: disable=superfluous-parens
369      found_difference = True
370    print(("* List before change contains entries for the following variables "
371           "that the list after the change doesn't:\n%s") % _array_to_string(
372               before_after_diff, ordered=True))
373  if after_before_diff:
374    if not found_difference:
375      print(diff_string)  # pylint: disable=superfluous-parens
376      found_difference = True
377    print(("* List after change contains entries for the following variables "
378           "that the list before the change doesn't:\n%s") % _array_to_string(
379               after_before_diff, ordered=True))
380
381  for variable in name_to_variable_before:
382    value_before = name_to_variable_before[variable]
383    value_after = name_to_variable_after.get(variable, None)
384    if value_after and value_after != value_before:
385      if not found_difference:
386        print(diff_string)  # pylint: disable=superfluous-parens
387        found_difference = True
388      print(
389          ("* Value for variable '%s' differs before and after the change:\n"
390           "Value before change:\t'%s'\n"
391           "Value after change:\t'%s'") % (variable, value_before, value_after))
392  if found_difference:
393    print("")  # pylint: disable=superfluous-parens
394  return found_difference
395
396
397def _compare_cxx_builtin_include_directories(directories_before,
398                                             directories_after):
399  if directories_before != directories_after:
400    print(("Difference in 'cxx_builtin_include_directory' field:\n"
401           "List of elements before change:\n%s\n"
402           "List of elements after change:\n%s\n") %
403          (_array_to_string(directories_before),
404           _array_to_string(directories_after)))
405    return True
406  return False
407
408
409def _compare_artifact_name_patterns(artifact_name_patterns_before,
410                                    artifact_name_patterns_after):
411  """Compares two "CToolchain.ArtifactNamePattern" lists."""
412  category_to_values_before = {}
413  category_to_values_after = {}
414  for name_pattern in artifact_name_patterns_before:
415    category_to_values_before[name_pattern.category_name] = (
416        name_pattern.prefix, name_pattern.extension)
417  for name_pattern in artifact_name_patterns_after:
418    category_to_values_after[name_pattern.category_name] = (
419        name_pattern.prefix, name_pattern.extension)
420
421  category_names_before = set(category_to_values_before.keys())
422  category_names_after = set(category_to_values_after.keys())
423
424  before_after_diff = category_names_before - category_names_after
425  after_before_diff = category_names_after - category_names_before
426
427  diff_string = "Difference in 'artifact_name_pattern' field:"
428  found_difference = False
429  if before_after_diff:
430    if not found_difference:
431      print(diff_string)  # pylint: disable=superfluous-parens
432      found_difference = True
433    print(("* List before change contains entries for the following categories "
434           "that the list after the change doesn't:\n%s") % _array_to_string(
435               before_after_diff, ordered=True))
436  if after_before_diff:
437    if not found_difference:
438      print(diff_string)  # pylint: disable=superfluous-parens
439      found_difference = True
440    print(("* List after change contains entries for the following categories "
441           "that the list before the change doesn't:\n%s") % _array_to_string(
442               after_before_diff, ordered=True))
443
444  for category in category_to_values_before:
445    value_before = category_to_values_before[category]
446    value_after = category_to_values_after.get(category, None)
447    if value_after and value_after != value_before:
448      if not found_difference:
449        print(diff_string)  # pylint: disable=superfluous-parens
450        found_difference = True
451      print(("* Value for category '%s' differs before and after the change:\n"
452             "Value before change:\tprefix:'%s'\textension:'%s'\n"
453             "Value after change:\tprefix:'%s'\textension:'%s'") %
454            (category, value_before[0], value_before[1], value_after[0],
455             value_after[1]))
456  if found_difference:
457    print("")  # pylint: disable=superfluous-parens
458  return found_difference
459
460
461def compare_ctoolchains(toolchain_before, toolchain_after):
462  """Compares two CToolchains."""
463  found_difference = False
464  if (toolchain_before.toolchain_identifier !=
465      toolchain_after.toolchain_identifier):
466    _print_difference("toolchain_identifier",
467                      toolchain_before.toolchain_identifier,
468                      toolchain_after.toolchain_identifier)
469  if toolchain_before.host_system_name != toolchain_after.host_system_name:
470    _print_difference("host_system_name", toolchain_before.host_system_name,
471                      toolchain_after.host_system_name)
472    found_difference = True
473  if toolchain_before.target_system_name != toolchain_after.target_system_name:
474    _print_difference("target_system_name", toolchain_before.target_system_name,
475                      toolchain_after.target_system_name)
476    found_difference = True
477  if toolchain_before.target_cpu != toolchain_after.target_cpu:
478    _print_difference("target_cpu", toolchain_before.target_cpu,
479                      toolchain_after.target_cpu)
480    found_difference = True
481  if toolchain_before.target_libc != toolchain_after.target_libc:
482    _print_difference("target_libc", toolchain_before.target_libc,
483                      toolchain_after.target_libc)
484    found_difference = True
485  if toolchain_before.compiler != toolchain_after.compiler:
486    _print_difference("compiler", toolchain_before.compiler,
487                      toolchain_after.compiler)
488    found_difference = True
489  if toolchain_before.abi_version != toolchain_after.abi_version:
490    _print_difference("abi_version", toolchain_before.abi_version,
491                      toolchain_after.abi_version)
492    found_difference = True
493  if toolchain_before.abi_libc_version != toolchain_after.abi_libc_version:
494    _print_difference("abi_libc_version", toolchain_before.abi_libc_version,
495                      toolchain_after.abi_libc_version)
496    found_difference = True
497  if toolchain_before.cc_target_os != toolchain_after.cc_target_os:
498    _print_difference("cc_target_os", toolchain_before.cc_target_os,
499                      toolchain_after.cc_target_os)
500    found_difference = True
501  if toolchain_before.builtin_sysroot != toolchain_after.builtin_sysroot:
502    _print_difference("builtin_sysroot", toolchain_before.builtin_sysroot,
503                      toolchain_after.builtin_sysroot)
504    found_difference = True
505  found_difference = _compare_features(
506      toolchain_before.feature, toolchain_after.feature) or found_difference
507  found_difference = _compare_action_configs(
508      toolchain_before.action_config,
509      toolchain_after.action_config) or found_difference
510  found_difference = _compare_tool_paths(
511      toolchain_before.tool_path, toolchain_after.tool_path) or found_difference
512  found_difference = _compare_cxx_builtin_include_directories(
513      toolchain_before.cxx_builtin_include_directory,
514      toolchain_after.cxx_builtin_include_directory) or found_difference
515  found_difference = _compare_make_variables(
516      toolchain_before.make_variable,
517      toolchain_after.make_variable) or found_difference
518  found_difference = _compare_artifact_name_patterns(
519      toolchain_before.artifact_name_pattern,
520      toolchain_after.artifact_name_pattern) or found_difference
521  if not found_difference:
522    print("No difference")  # pylint: disable=superfluous-parens
523  return found_difference
524