xref: /aosp_15_r20/build/bazel/scripts/difftool/action_diff_notebook.py (revision 7594170e27e0732bc44b93d1440d87a54b6ffe7c)
1*7594170eSAndroid Build Coastguard Worker#!/usr/bin/env python3
2*7594170eSAndroid Build Coastguard Worker#
3*7594170eSAndroid Build Coastguard Worker# Copyright (C) 2022 The Android Open Source Project
4*7594170eSAndroid Build Coastguard Worker#
5*7594170eSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License");
6*7594170eSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License.
7*7594170eSAndroid Build Coastguard Worker# You may obtain a copy of the License at
8*7594170eSAndroid Build Coastguard Worker#
9*7594170eSAndroid Build Coastguard Worker#   http://www.apache.org/licenses/LICENSE-2.0
10*7594170eSAndroid Build Coastguard Worker#
11*7594170eSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software
12*7594170eSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS,
13*7594170eSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14*7594170eSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and
15*7594170eSAndroid Build Coastguard Worker# limitations under the License.
16*7594170eSAndroid Build Coastguard Worker"""Provides useful diff information for build artifacts.
17*7594170eSAndroid Build Coastguard Worker
18*7594170eSAndroid Build Coastguard WorkerThis file is intended to be used like a Jupyter notebook. Since there isn't a
19*7594170eSAndroid Build Coastguard Workerone-to-one pairing between Soong intermediate artifacts and Bazel intermediate
20*7594170eSAndroid Build Coastguard Workerartifacts, I've found it's easiest to automate some of the diffing while
21*7594170eSAndroid Build Coastguard Workerleaving room for manual selection of what targets/artifacts to compare.
22*7594170eSAndroid Build Coastguard Worker
23*7594170eSAndroid Build Coastguard WorkerIn this file, the runnable sections are separated by the `# %%` identifier, and
24*7594170eSAndroid Build Coastguard Workera compatible editor should be able to run those code blocks independently. I
25*7594170eSAndroid Build Coastguard Workerused VSCode during development, but this functionality also exists in other
26*7594170eSAndroid Build Coastguard Workereditors via plugins.
27*7594170eSAndroid Build Coastguard Worker
28*7594170eSAndroid Build Coastguard WorkerThere are some comments throughout to give an idea of how this notebook can be
29*7594170eSAndroid Build Coastguard Workerused.
30*7594170eSAndroid Build Coastguard Worker"""
31*7594170eSAndroid Build Coastguard Worker
32*7594170eSAndroid Build Coastguard Worker# %%
33*7594170eSAndroid Build Coastguard Workerimport os
34*7594170eSAndroid Build Coastguard Workerimport pathlib
35*7594170eSAndroid Build Coastguard Worker
36*7594170eSAndroid Build Coastguard Worker# This script should be run from the $TOP directory
37*7594170eSAndroid Build Coastguard WorkerANDROID_CHECKOUT_PATH = pathlib.Path(".").resolve()
38*7594170eSAndroid Build Coastguard Workeros.chdir(ANDROID_CHECKOUT_PATH)
39*7594170eSAndroid Build Coastguard Worker
40*7594170eSAndroid Build Coastguard Worker# %%
41*7594170eSAndroid Build Coastguard Workerimport subprocess
42*7594170eSAndroid Build Coastguard Worker
43*7594170eSAndroid Build Coastguard Workeros.chdir(os.path.join(ANDROID_CHECKOUT_PATH, "build/bazel/scripts/difftool"))
44*7594170eSAndroid Build Coastguard Workerimport difftool
45*7594170eSAndroid Build Coastguard Workerimport commands
46*7594170eSAndroid Build Coastguard Workerimport importlib
47*7594170eSAndroid Build Coastguard Worker
48*7594170eSAndroid Build Coastguard Worker# Python doesn't reload packages that have already been imported unless you
49*7594170eSAndroid Build Coastguard Worker# use importlib to explicitly reload them
50*7594170eSAndroid Build Coastguard Workerimportlib.reload(difftool)
51*7594170eSAndroid Build Coastguard Workerimportlib.reload(commands)
52*7594170eSAndroid Build Coastguard Workeros.chdir(ANDROID_CHECKOUT_PATH)
53*7594170eSAndroid Build Coastguard Worker
54*7594170eSAndroid Build Coastguard Worker# %%
55*7594170eSAndroid Build Coastguard WorkerLUNCH_TARGET = "aosp_arm64"
56*7594170eSAndroid Build Coastguard WorkerTARGET_BUILD_VARIANT = "userdebug"
57*7594170eSAndroid Build Coastguard Worker
58*7594170eSAndroid Build Coastguard Workersubprocess.run([
59*7594170eSAndroid Build Coastguard Worker    "build/soong/soong_ui.bash",
60*7594170eSAndroid Build Coastguard Worker    "--make-mode",
61*7594170eSAndroid Build Coastguard Worker    f"TARGET_PRODUCT={LUNCH_TARGET}",
62*7594170eSAndroid Build Coastguard Worker    f"TARGET_BUILD_VARIANT={TARGET_BUILD_VARIANT}",
63*7594170eSAndroid Build Coastguard Worker    "--skip-soong-tests",
64*7594170eSAndroid Build Coastguard Worker    "bp2build",
65*7594170eSAndroid Build Coastguard Worker    "nothing",
66*7594170eSAndroid Build Coastguard Worker])
67*7594170eSAndroid Build Coastguard Worker
68*7594170eSAndroid Build Coastguard Worker
69*7594170eSAndroid Build Coastguard Worker# %%
70*7594170eSAndroid Build Coastguard Workerdef get_bazel_actions(
71*7594170eSAndroid Build Coastguard Worker    *, expr: str, config: str, mnemonic: str, additional_args: list[str] = []
72*7594170eSAndroid Build Coastguard Worker):
73*7594170eSAndroid Build Coastguard Worker  return difftool.collect_commands_bazel(
74*7594170eSAndroid Build Coastguard Worker      expr, config, mnemonic, *additional_args
75*7594170eSAndroid Build Coastguard Worker  )
76*7594170eSAndroid Build Coastguard Worker
77*7594170eSAndroid Build Coastguard Worker
78*7594170eSAndroid Build Coastguard Workerdef get_ninja_actions(*, lunch_target: str, target: str, mnemonic: str):
79*7594170eSAndroid Build Coastguard Worker  ninja_output = difftool.collect_commands_ninja(
80*7594170eSAndroid Build Coastguard Worker      pathlib.Path(f"out/combined-{lunch_target}.ninja").resolve(),
81*7594170eSAndroid Build Coastguard Worker      pathlib.Path(target),
82*7594170eSAndroid Build Coastguard Worker      pathlib.Path("prebuilts/build-tools/linux-x86/bin/ninja").resolve(),
83*7594170eSAndroid Build Coastguard Worker  )
84*7594170eSAndroid Build Coastguard Worker  return [l for l in ninja_output if mnemonic in l]
85*7594170eSAndroid Build Coastguard Worker
86*7594170eSAndroid Build Coastguard Worker# %%
87*7594170eSAndroid Build Coastguard Worker# Example 1: Comparing link actions
88*7594170eSAndroid Build Coastguard Worker# This example gets all of the "CppLink" actions from the adb_test module, and
89*7594170eSAndroid Build Coastguard Worker# also gets the build actions that are needed to build the same module from
90*7594170eSAndroid Build Coastguard Worker# through Ninja.
91*7594170eSAndroid Build Coastguard Worker#
92*7594170eSAndroid Build Coastguard Worker# After getting the action lists from each build tool, you can inspect the list
93*7594170eSAndroid Build Coastguard Worker# to find the particular action you're interested in diffing. In this case, there
94*7594170eSAndroid Build Coastguard Worker# was only 1 CppLink action from Bazel. The corresponding link action from Ninja
95*7594170eSAndroid Build Coastguard Worker# happened to be the last one (this is pretty typical).
96*7594170eSAndroid Build Coastguard Worker#
97*7594170eSAndroid Build Coastguard Worker# Then we set a new variable to keep track of each of these action strings.
98*7594170eSAndroid Build Coastguard Worker
99*7594170eSAndroid Build Coastguard Workerbzl_actions = get_bazel_actions(
100*7594170eSAndroid Build Coastguard Worker    config="linux_x86_64",
101*7594170eSAndroid Build Coastguard Worker    expr="//packages/modules/adb:adb_test__test_binary_unstripped",
102*7594170eSAndroid Build Coastguard Worker    mnemonic="CppLink",
103*7594170eSAndroid Build Coastguard Worker)
104*7594170eSAndroid Build Coastguard Workerninja_actions = get_ninja_actions(
105*7594170eSAndroid Build Coastguard Worker    lunch_target=LUNCH_TARGET,
106*7594170eSAndroid Build Coastguard Worker    target="out/soong/.intermediates/packages/modules/adb/adb_test/linux_glibc_x86_64/adb_test",
107*7594170eSAndroid Build Coastguard Worker    mnemonic="clang++",
108*7594170eSAndroid Build Coastguard Worker)
109*7594170eSAndroid Build Coastguard Workerbazel_action = bzl_actions[0]["arguments"]
110*7594170eSAndroid Build Coastguard Workerninja_action = ninja_actions[-1].split()
111*7594170eSAndroid Build Coastguard Worker
112*7594170eSAndroid Build Coastguard Worker# %%
113*7594170eSAndroid Build Coastguard Worker# Example 2: Comparing compile actions
114*7594170eSAndroid Build Coastguard Worker# This example is similar and gets all of the "CppCompile" actions from the
115*7594170eSAndroid Build Coastguard Worker# internal sub-target of adb_test. There is a "CppCompile" action for every
116*7594170eSAndroid Build Coastguard Worker# .cc file that goes into the target, so we just pick one of these files and
117*7594170eSAndroid Build Coastguard Worker# get the corresponding compile action from Ninja for this file.
118*7594170eSAndroid Build Coastguard Worker#
119*7594170eSAndroid Build Coastguard Worker# Similarly, we select an action from the Bazel list and its corresponding
120*7594170eSAndroid Build Coastguard Worker# Ninja action.
121*7594170eSAndroid Build Coastguard Worker
122*7594170eSAndroid Build Coastguard Worker# bzl_actions = get_bazel_actions(
123*7594170eSAndroid Build Coastguard Worker#     config="linux_x86_64",
124*7594170eSAndroid Build Coastguard Worker#     expr="//packages/modules/adb:adb_test__test_binary__internal_root_cpp",
125*7594170eSAndroid Build Coastguard Worker#     mnemonic="CppCompile",
126*7594170eSAndroid Build Coastguard Worker# )
127*7594170eSAndroid Build Coastguard Worker# ninja_actions = get_ninja_actions(
128*7594170eSAndroid Build Coastguard Worker#     lunch_target=LUNCH_TARGET,
129*7594170eSAndroid Build Coastguard Worker#     target="out/soong/.intermediates/packages/modules/adb/adb_test/linux_glibc_x86_64/obj/packages/modules/adb/adb_io_test.o",
130*7594170eSAndroid Build Coastguard Worker#     mnemonic="clang++",
131*7594170eSAndroid Build Coastguard Worker# )
132*7594170eSAndroid Build Coastguard Worker# bazel_action = bzl_actions[0]["arguments"]
133*7594170eSAndroid Build Coastguard Worker# ninja_action = ninja_actions[-1].split()
134*7594170eSAndroid Build Coastguard Worker
135*7594170eSAndroid Build Coastguard Worker# %%
136*7594170eSAndroid Build Coastguard Worker# Example 3: more complex expressions in the Bazel action
137*7594170eSAndroid Build Coastguard Worker# This example gets all of the "CppCompile" actions from the deps of everything
138*7594170eSAndroid Build Coastguard Worker# under the //packages/modules/adb package, but it uses the additional_args
139*7594170eSAndroid Build Coastguard Worker# to exclude "manual" internal targets.
140*7594170eSAndroid Build Coastguard Worker
141*7594170eSAndroid Build Coastguard Worker# bzl_actions = get_bazel_actions(
142*7594170eSAndroid Build Coastguard Worker#     config="linux_x86_64",
143*7594170eSAndroid Build Coastguard Worker#     expr="deps(//packages/modules/adb/...)",
144*7594170eSAndroid Build Coastguard Worker#     mnemonic="CppCompile",
145*7594170eSAndroid Build Coastguard Worker#     additional_args=[
146*7594170eSAndroid Build Coastguard Worker#         "--build_tag_filters=-manual",
147*7594170eSAndroid Build Coastguard Worker#     ],
148*7594170eSAndroid Build Coastguard Worker# )
149*7594170eSAndroid Build Coastguard Worker
150*7594170eSAndroid Build Coastguard Worker# %%
151*7594170eSAndroid Build Coastguard Worker# Once we have the command-line string for each action from Bazel and Ninja,
152*7594170eSAndroid Build Coastguard Worker# we can use difftool to parse and compare the actions.
153*7594170eSAndroid Build Coastguard Workerninja_action = commands.expand_rsp(ninja_action)
154*7594170eSAndroid Build Coastguard Workerbzl_rich_commands = difftool.rich_command_info(" ".join(bazel_action))
155*7594170eSAndroid Build Coastguard Workerninja_rich_commands = difftool.rich_command_info(" ".join(ninja_action))
156*7594170eSAndroid Build Coastguard Worker
157*7594170eSAndroid Build Coastguard Workerprint("\nBazel args:")
158*7594170eSAndroid Build Coastguard Workerprint(" \\\n\t".join([bzl_rich_commands.tool] + bzl_rich_commands.args))
159*7594170eSAndroid Build Coastguard Workerprint("\nSoong args:")
160*7594170eSAndroid Build Coastguard Workerprint(" \\\n\t".join([ninja_rich_commands.tool] + ninja_rich_commands.args))
161*7594170eSAndroid Build Coastguard Worker
162*7594170eSAndroid Build Coastguard Workerbzl_only = bzl_rich_commands.compare(ninja_rich_commands)
163*7594170eSAndroid Build Coastguard Workersoong_only = ninja_rich_commands.compare(bzl_rich_commands)
164*7594170eSAndroid Build Coastguard Workerprint("\nIn Bazel, not Soong:")
165*7594170eSAndroid Build Coastguard Workerprint(bzl_only)
166*7594170eSAndroid Build Coastguard Workerprint("\nIn Soong, not Bazel:")
167*7594170eSAndroid Build Coastguard Workerprint(soong_only)
168*7594170eSAndroid Build Coastguard Worker
169*7594170eSAndroid Build Coastguard Worker# %%
170*7594170eSAndroid Build Coastguard Worker# Now that we've diffed the action strings, it is sometimes useful to also
171*7594170eSAndroid Build Coastguard Worker# diff the paths that go into the action. This helps us narrow down diffs
172*7594170eSAndroid Build Coastguard Worker# in a module that are created in their dependencies. This section attempts
173*7594170eSAndroid Build Coastguard Worker# to match paths from the Bazel action to corresponding paths in the Ninja
174*7594170eSAndroid Build Coastguard Worker# action, and the runs difftool on these paths.
175*7594170eSAndroid Build Coastguard Workerbzl_paths, _ = commands.extract_paths_from_action_args(bazel_action)
176*7594170eSAndroid Build Coastguard Workerninja_paths, _ = commands.extract_paths_from_action_args(ninja_action)
177*7594170eSAndroid Build Coastguard Workerunmatched_paths = []
178*7594170eSAndroid Build Coastguard Workerfor p1, p2 in commands.match_paths(bzl_paths, ninja_paths).items():
179*7594170eSAndroid Build Coastguard Worker  if p2 is None:
180*7594170eSAndroid Build Coastguard Worker    unmatched_paths.append(p1)
181*7594170eSAndroid Build Coastguard Worker    continue
182*7594170eSAndroid Build Coastguard Worker  diff = difftool.file_differences(
183*7594170eSAndroid Build Coastguard Worker      pathlib.Path(p1).resolve(),
184*7594170eSAndroid Build Coastguard Worker      pathlib.Path(p2).resolve(),
185*7594170eSAndroid Build Coastguard Worker      level=difftool.DiffLevel.FINE,
186*7594170eSAndroid Build Coastguard Worker  )
187*7594170eSAndroid Build Coastguard Worker  for row in diff:
188*7594170eSAndroid Build Coastguard Worker    print(row)
189*7594170eSAndroid Build Coastguard Workerif unmatched_paths:
190*7594170eSAndroid Build Coastguard Worker  # Since the test for file paths looks for existing files, this matching won't
191*7594170eSAndroid Build Coastguard Worker  # work if the Soong artifacts don't exist.
192*7594170eSAndroid Build Coastguard Worker  print(
193*7594170eSAndroid Build Coastguard Worker      "Found some Bazel paths that didn't have a good match in Soong "
194*7594170eSAndroid Build Coastguard Worker      + "intermediates. Did you run `m`?"
195*7594170eSAndroid Build Coastguard Worker  )
196*7594170eSAndroid Build Coastguard Worker  print("Unmatched paths:")
197*7594170eSAndroid Build Coastguard Worker  for i in unmatched_paths:
198*7594170eSAndroid Build Coastguard Worker    print("\t" + i)
199