blob: a9f0c9b506d769bfaad29e04061355cb1817b19b [file] [log] [blame]
Liz Kammer767fad42023-06-09 11:23:15 -04001#!/usr/bin/env python3
2
3# Copyright (C) 2023 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17import argparse
18import collections
19import json
20import os.path
21import subprocess
22import tempfile
23
24SRC_ROOT_DIR = os.path.abspath(__file__ + "/../../../..")
25
26
27def _module_graph_path(out_dir):
28 return os.path.join(SRC_ROOT_DIR, out_dir, "soong", "module-actions.json")
29
30
31def _build_with_soong(targets, target_product, out_dir, extra_env={}):
32 env = {
33 "TARGET_PRODUCT": target_product,
34 "TARGET_BUILD_VARIANT": "userdebug",
35 }
36 env.update(os.environ)
37 env.update(extra_env)
38 args = [
39 "build/soong/soong_ui.bash",
40 "--make-mode",
41 "--skip-soong-tests",
42 ]
43 args.extend(targets)
44 try:
45 out = subprocess.check_output(
46 args,
47 cwd=SRC_ROOT_DIR,
48 env=env,
49 )
50 except subprocess.CalledProcessError as e:
51 print(e)
52 print(e.stdout)
53 print(e.stderr)
54 exit(1)
55
56
57def _find_outputs_for_modules(modules, out_dir, target_product):
58 module_path = os.path.join(
59 SRC_ROOT_DIR, out_dir, "soong", "module-actions.json"
60 )
61
62 if not os.path.exists(module_path):
63 _build_with_soong(["json-module-graph"], target_product, out_dir)
64
65 action_graph = json.load(open(_module_graph_path(out_dir)))
66
67 module_to_outs = collections.defaultdict(set)
68 for mod in action_graph:
69 name = mod["Name"]
70 if name in modules:
71 for act in mod["Module"]["Actions"]:
Jason Macnak394f1b72023-06-15 09:28:26 -070072 if "}generate" in act["Desc"]:
Liz Kammer767fad42023-06-09 11:23:15 -040073 module_to_outs[name].update(act["Outputs"])
74 return module_to_outs
75
76
77def _store_outputs_to_tmp(output_files):
78 try:
79 tempdir = tempfile.TemporaryDirectory()
80 for f in output_files:
81 out = subprocess.check_output(
82 ["cp", "--parents", f, tempdir.name],
83 cwd=SRC_ROOT_DIR,
84 )
85 return tempdir
86 except subprocess.CalledProcessError as e:
87 print(e)
88 print(e.stdout)
89 print(e.stderr)
90
91
92def _diff_outs(file1, file2, show_diff):
Jason Macnak394f1b72023-06-15 09:28:26 -070093 output = None
Liz Kammer767fad42023-06-09 11:23:15 -040094 base_args = ["diff"]
95 if not show_diff:
96 base_args.append("--brief")
97 try:
98 args = base_args + [file1, file2]
99 output = subprocess.check_output(
100 args,
101 cwd=SRC_ROOT_DIR,
102 )
103 except subprocess.CalledProcessError as e:
104 if e.returncode == 1:
105 if show_diff:
106 return output
107 return True
108 return None
109
110
111def _compare_outputs(module_to_outs, tempdir, show_diff):
112 different_modules = collections.defaultdict(list)
113 for module, outs in module_to_outs.items():
114 for out in outs:
115 output = None
116 diff = _diff_outs(os.path.join(tempdir.name, out), out, show_diff)
117 if diff:
118 different_modules[module].append(diff)
119
120 tempdir.cleanup()
121 return different_modules
122
123
124def main():
125 parser = argparse.ArgumentParser()
126 parser.add_argument(
127 "--target_product",
128 "-t",
129 default="aosp_cf_arm64_phone",
130 help="optional, target product, always runs as eng",
131 )
132 parser.add_argument(
133 "modules",
134 nargs="+",
135 help="modules to compare builds with genrule sandboxing enabled/not",
136 )
137 parser.add_argument(
138 "--show-diff",
139 "-d",
140 action="store_true",
141 required=False,
142 help="whether to display differing files",
143 )
Liz Kammer2fb361c2023-06-09 11:29:48 -0400144 parser.add_argument(
145 "--output-paths-only",
146 "-o",
147 action="store_true",
148 required=False,
149 help="Whether to only return the output paths per module",
150 )
Liz Kammer767fad42023-06-09 11:23:15 -0400151 args = parser.parse_args()
152
153 out_dir = os.environ.get("OUT_DIR", "out")
154 target_product = args.target_product
155 modules = set(args.modules)
156
157 module_to_outs = _find_outputs_for_modules(modules, out_dir, target_product)
Jason Macnak394f1b72023-06-15 09:28:26 -0700158 if not module_to_outs:
159 print("No outputs found")
160 exit(1)
161
Liz Kammer2fb361c2023-06-09 11:29:48 -0400162 if args.output_paths_only:
163 for m, o in module_to_outs.items():
164 print(f"{m} outputs: {o}")
165 exit(0)
166
Liz Kammer767fad42023-06-09 11:23:15 -0400167 all_outs = set()
168 for outs in module_to_outs.values():
169 all_outs.update(outs)
170 print("build without sandboxing")
171 _build_with_soong(list(all_outs), target_product, out_dir)
172 tempdir = _store_outputs_to_tmp(all_outs)
173 print("build with sandboxing")
174 _build_with_soong(
175 list(all_outs),
176 target_product,
177 out_dir,
178 extra_env={"GENRULE_SANDBOXING": "true"},
179 )
180 diffs = _compare_outputs(module_to_outs, tempdir, args.show_diff)
181 if len(diffs) == 0:
182 print("All modules are correct")
183 elif args.show_diff:
184 for m, d in diffs.items():
185 print(f"Module {m} has diffs {d}")
186 else:
187 print(f"Modules {list(diffs.keys())} have diffs")
188
189
190if __name__ == "__main__":
191 main()