blob: 697fc26e89bef1dbfd7bd9781dec5d0aca94436b [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"]:
72 if "}generate " in act["Desc"]:
73 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):
93 base_args = ["diff"]
94 if not show_diff:
95 base_args.append("--brief")
96 try:
97 args = base_args + [file1, file2]
98 output = subprocess.check_output(
99 args,
100 cwd=SRC_ROOT_DIR,
101 )
102 except subprocess.CalledProcessError as e:
103 if e.returncode == 1:
104 if show_diff:
105 return output
106 return True
107 return None
108
109
110def _compare_outputs(module_to_outs, tempdir, show_diff):
111 different_modules = collections.defaultdict(list)
112 for module, outs in module_to_outs.items():
113 for out in outs:
114 output = None
115 diff = _diff_outs(os.path.join(tempdir.name, out), out, show_diff)
116 if diff:
117 different_modules[module].append(diff)
118
119 tempdir.cleanup()
120 return different_modules
121
122
123def main():
124 parser = argparse.ArgumentParser()
125 parser.add_argument(
126 "--target_product",
127 "-t",
128 default="aosp_cf_arm64_phone",
129 help="optional, target product, always runs as eng",
130 )
131 parser.add_argument(
132 "modules",
133 nargs="+",
134 help="modules to compare builds with genrule sandboxing enabled/not",
135 )
136 parser.add_argument(
137 "--show-diff",
138 "-d",
139 action="store_true",
140 required=False,
141 help="whether to display differing files",
142 )
Liz Kammer2fb361c2023-06-09 11:29:48 -0400143 parser.add_argument(
144 "--output-paths-only",
145 "-o",
146 action="store_true",
147 required=False,
148 help="Whether to only return the output paths per module",
149 )
Liz Kammer767fad42023-06-09 11:23:15 -0400150 args = parser.parse_args()
151
152 out_dir = os.environ.get("OUT_DIR", "out")
153 target_product = args.target_product
154 modules = set(args.modules)
155
156 module_to_outs = _find_outputs_for_modules(modules, out_dir, target_product)
Liz Kammer2fb361c2023-06-09 11:29:48 -0400157 if args.output_paths_only:
158 for m, o in module_to_outs.items():
159 print(f"{m} outputs: {o}")
160 exit(0)
161
Liz Kammer767fad42023-06-09 11:23:15 -0400162 all_outs = set()
163 for outs in module_to_outs.values():
164 all_outs.update(outs)
165 print("build without sandboxing")
166 _build_with_soong(list(all_outs), target_product, out_dir)
167 tempdir = _store_outputs_to_tmp(all_outs)
168 print("build with sandboxing")
169 _build_with_soong(
170 list(all_outs),
171 target_product,
172 out_dir,
173 extra_env={"GENRULE_SANDBOXING": "true"},
174 )
175 diffs = _compare_outputs(module_to_outs, tempdir, args.show_diff)
176 if len(diffs) == 0:
177 print("All modules are correct")
178 elif args.show_diff:
179 for m, d in diffs.items():
180 print(f"Module {m} has diffs {d}")
181 else:
182 print(f"Modules {list(diffs.keys())} have diffs")
183
184
185if __name__ == "__main__":
186 main()