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