xref: /aosp_15_r20/external/bazelbuild-rules_cc/tools/migration/ctoolchain_comparator.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.
14r"""A script that compares 2 CToolchains from proto format.
15
16This script accepts two files in either a CROSSTOOL proto text format or a
17CToolchain proto text format. It then locates the CToolchains with the given
18toolchain_identifier and checks if the resulting CToolchain objects in Java
19are the same.
20
21Example usage:
22
23bazel run \
24@rules_cc//tools/migration:ctoolchain_comparator -- \
25--before=/path/to/CROSSTOOL1 \
26--after=/path/to/CROSSTOOL2 \
27--toolchain_identifier=id
28"""
29
30import os
31from absl import app
32from absl import flags
33from google.protobuf import text_format
34from third_party.com.github.bazelbuild.bazel.src.main.protobuf import crosstool_config_pb2
35from tools.migration.ctoolchain_comparator_lib import compare_ctoolchains
36
37flags.DEFINE_string(
38    "before", None,
39    ("A text proto file containing the relevant CTooclchain before the change, "
40     "either a CROSSTOOL file or a single CToolchain proto text"))
41flags.DEFINE_string(
42    "after", None,
43    ("A text proto file containing the relevant CToolchain after the change, "
44     "either a CROSSTOOL file or a single CToolchain proto text"))
45flags.DEFINE_string("toolchain_identifier", None,
46                    "The identifier of the CToolchain that is being compared.")
47flags.mark_flag_as_required("before")
48flags.mark_flag_as_required("after")
49
50
51def _to_absolute_path(path):
52  path = os.path.expanduser(path)
53  if os.path.isabs(path):
54    return path
55  else:
56    if "BUILD_WORKING_DIRECTORY" in os.environ:
57      return os.path.join(os.environ["BUILD_WORKING_DIRECTORY"], path)
58    else:
59      return path
60
61
62def _find_toolchain(crosstool, toolchain_identifier):
63  for toolchain in crosstool.toolchain:
64    if toolchain.toolchain_identifier == toolchain_identifier:
65      return toolchain
66  return None
67
68
69def _read_crosstool_or_ctoolchain_proto(input_file, toolchain_identifier=None):
70  """Reads a proto file and finds the CToolchain with the given identifier."""
71  with open(input_file, "r") as f:
72    text = f.read()
73  crosstool_release = crosstool_config_pb2.CrosstoolRelease()
74  c_toolchain = crosstool_config_pb2.CToolchain()
75  try:
76    text_format.Merge(text, crosstool_release)
77    if toolchain_identifier is None:
78      print("CROSSTOOL proto needs a 'toolchain_identifier' specified in "
79            "order to be able to select the right toolchain for comparison.")
80      return None
81    toolchain = _find_toolchain(crosstool_release, toolchain_identifier)
82    if toolchain is None:
83      print(("Cannot find a CToolchain with an identifier '%s' in CROSSTOOL "
84             "file") % toolchain_identifier)
85      return None
86    return toolchain
87  except text_format.ParseError as crosstool_error:
88    try:
89      text_format.Merge(text, c_toolchain)
90      if (toolchain_identifier is not None and
91          c_toolchain.toolchain_identifier != toolchain_identifier):
92        print(("Expected CToolchain with identifier '%s', got CToolchain with "
93               "identifier '%s'" % (toolchain_identifier,
94                                    c_toolchain.toolchain_identifier)))
95        return None
96      return c_toolchain
97    except text_format.ParseError as toolchain_error:
98      print(("Error parsing file '%s':" % input_file))  # pylint: disable=superfluous-parens
99      print("Attempt to parse it as a CROSSTOOL proto:")  # pylint: disable=superfluous-parens
100      print(crosstool_error)  # pylint: disable=superfluous-parens
101      print("Attempt to parse it as a CToolchain proto:")  # pylint: disable=superfluous-parens
102      print(toolchain_error)  # pylint: disable=superfluous-parens
103      return None
104
105
106def main(unused_argv):
107
108  before_file = _to_absolute_path(flags.FLAGS.before)
109  after_file = _to_absolute_path(flags.FLAGS.after)
110  toolchain_identifier = flags.FLAGS.toolchain_identifier
111
112  toolchain_before = _read_crosstool_or_ctoolchain_proto(
113      before_file, toolchain_identifier)
114  toolchain_after = _read_crosstool_or_ctoolchain_proto(after_file,
115                                                        toolchain_identifier)
116
117  if not toolchain_before or not toolchain_after:
118    print("There was an error getting the required toolchains.")
119    exit(1)
120
121  found_difference = compare_ctoolchains(toolchain_before, toolchain_after)
122  if found_difference:
123    exit(1)
124
125
126if __name__ == "__main__":
127  app.run(main)
128