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