genrule_sandbox_test.py 4.9 KB

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