#!/usr/bin/env python3 # # Copyright 2025 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. #
import argparse import glob import hashlib import os import shutil import stat import struct import subprocess import sys import zipfile
from ninja_determinism_test import Product, get_top, transitively_included_ninja_files
# Equivalent of soong's IsEnvTrue def is_env_true(e: str) -> bool:
value = os.environ.get(e, '').lower() return value == '1'or value == 'y'or value == 'yes'or value == 'on'or value == 'true'
def run_build_target_files_zip(product: Product, soong_only: bool) -> bool: """Runs a build and returns if it succeeded or not."""
soong_only_arg = '--no-soong-only' if soong_only:
soong_only_arg = '--soong-only'
out_dir = os.getenv('OUT_DIR', 'out')
ifnot os.path.exists(out_dir):
os.mkdir(out_dir)
# inner function so we can early return but not miss the final # move_artifacts_to_subfolder() def inner(): with open(os.path.join(out_dir, 'build.log'), 'wb') as f:
result = subprocess.run([ 'build/soong/soong_ui.bash', '--make-mode', 'USE_RBE=true', 'BUILD_DATETIME=1', 'USE_FIXED_TIMESTAMP_IMG_FILES=true', 'DISABLE_NOTICE_XML_GENERATION=true',
f'TARGET_PRODUCT={product.product}',
f'TARGET_RELEASE={product.release}',
f'TARGET_BUILD_VARIANT={product.variant}', 'installclean',
soong_only_arg,
], stdout=f, stderr=subprocess.STDOUT, env=os.environ)
if result.returncode != 0: returnFalse
with open(os.path.join(out_dir, 'build.log'), 'wb') as f:
result = subprocess.run([ 'build/soong/soong_ui.bash', '--make-mode', 'USE_RBE=true', 'BUILD_DATETIME=1', 'USE_FIXED_TIMESTAMP_IMG_FILES=true', 'DISABLE_NOTICE_XML_GENERATION=true',
f'TARGET_PRODUCT={product.product}',
f'TARGET_RELEASE={product.release}',
f'TARGET_BUILD_VARIANT={product.variant}', 'target-files-package', 'target-files-package-soong-diff-test', 'droid', 'superimage',
soong_only_arg,
], stdout=f, stderr=subprocess.STDOUT, env=os.environ)
if result.returncode != 0: returnFalse
with open(os.path.join(out_dir, 'build.log2'), 'wb') as f: # Split the dist into a separate invocation to limit dist to target_files.zip # This is expected to be faster than disting all droid.
result = subprocess.run([ 'build/soong/soong_ui.bash', '--make-mode', 'USE_RBE=true', 'BUILD_DATETIME=1', 'USE_FIXED_TIMESTAMP_IMG_FILES=true', 'DISABLE_NOTICE_XML_GENERATION=true',
f'TARGET_PRODUCT={product.product}',
f'TARGET_RELEASE={product.release}',
f'TARGET_BUILD_VARIANT={product.variant}', 'target-files-package', 'target-files-package-soong-diff-test', 'dist',
soong_only_arg,
], stdout=f, stderr=subprocess.STDOUT, env=os.environ)
with open(os.path.join(out_dir, 'build.log2'), 'rb') as f:
log2_contents = f.read() with open(os.path.join(out_dir, 'build.log'), 'ab') as f:
f.write(b"\n")
f.write(log2_contents)
# These values are defined in build/soong/zip/zip.go
SHA_256_HEADER_ID = 0x4967
SHA_256_HEADER_SIGNATURE = 0x9514
def get_local_file_sha256_fields(zip_filepath: os.PathLike) -> dict[str, bytes]: ifnot os.path.exists(zip_filepath):
print(f"Error: File not found at {zip_filepath}", file=sys.stderr) returnNone
sha256_checksums: dict[str, bytes] = {}
with zipfile.ZipFile(zip_filepath, 'r') as zip_ref:
infolist = zip_ref.infolist()
for member_info in infolist: # Skip if the entry is a directory. if member_info.is_dir(): continue
local_extra_data = member_info.extra
i = 0
found_sha_in_file = None while i + 4 <= len(local_extra_data): # Need at least 4 (header ID + data size)
block_header_id, block_data_size = struct.unpack('<HH', local_extra_data[i:i+4])
current_block_end = i + 4 + block_data_size
# Check if the block is SHA256 block if block_header_id == SHA_256_HEADER_ID: if block_data_size >= 2:
data_bytes = local_extra_data[i+4 : current_block_end]
if found_sha_in_file:
sha256_checksums[member_info.filename] = found_sha_in_file elif member_info.external_attr != 0: # Upper 16 bits of external_attr are UNIX permissions. # If the file is a symlink then add its target as the value of the map.
mode = (member_info.external_attr >> 16) & 0xFFFF if stat.S_ISLNK(mode):
target = zip_ref.read(member_info.filename)
sha256_checksums[member_info.filename] = target else:
print(f"{member_info.filename} sha not found", file=sys.stderr)
zip_filename = os.path.join(subdistdir, "ninja_files.zip") with zipfile.ZipFile(zip_filename, 'w', compression=zipfile.ZIP_DEFLATED) as zipf: for file in files_to_zip:
zipf.write(filename=file, arcname=os.path.relpath(file, root_dir))
def compare_sha_maps(product: Product, soong_only_map: dict[str, bytes], soong_plus_make_map: dict[str, bytes]) -> bool: """Compares two sha maps and reports any missing or different entries."""
all_keys = sorted(list(soong_only_map.keys() | soong_plus_make_map.keys()))
all_identical = True with open(get_comparison_report_path(product), 'wt') as file: for key in all_keys:
allowlisted = key in SHA_DIFF_ALLOWLIST
allowlisted_str = "ALLOWLISTED"if allowlisted else"NOT ALLOWLISTED" if key notin soong_only_map:
print(f'{key} not found in soong only build target_files.zip ({allowlisted_str})', file=file)
all_identical = all_identical and allowlisted elif key notin soong_plus_make_map:
print(f'{key} not found in soong plus make build target_files.zip ({allowlisted_str})', file=file)
all_identical = all_identical and allowlisted elif soong_only_map[key] != soong_plus_make_map[key]:
print(f'{key} sha value differ between soong only build and soong plus make build ({allowlisted_str})', file=file)
all_identical = all_identical and allowlisted
return all_identical
def get_zip_sha_map(product: Product, soong_only: bool) -> dict[str, bytes]: """Runs the build and returns the map of entries to its SHA256 values of target_files.zip."""
subdistdir = get_sub_dist_dir(product, soong_only)
target_files_zip_glob = os.path.join(subdistdir, f'{product.product}-target_files-*.zip')
target_files_zip = glob.glob(target_files_zip_glob) if len(target_files_zip) != 1:
sys.exit(f'Could not find {target_files_zip_glob}')
zip_sha_map = get_local_file_sha256_fields(target_files_zip[0]) if zip_sha_map isNone:
sys.exit("Could not construct sha map for target_files.zip entries for soong only build")
# TODO (b/435530838): Remove this allowlist.
_INSTALLED_IMG_FILES_SHA_DIFF_ALLOWLIST = [ "userdata.img",
]
def get_installed_img_sha(path: str) -> str: """Returns the SHA256 value of a file."""
sha256_hash = hashlib.sha256()
chunk_size = 1024 * 1024# 1 MB with open(path, "rb") as f: for chunk in iter(lambda: f.read(chunk_size), b""):
sha256_hash.update(chunk) return sha256_hash.hexdigest()
def get_installed_img_sha_map(product: Product) -> dict[str, str]: """Returns the map of installed .img to its SHA256 value."""
out_dir = os.getenv('OUT_DIR', 'out')
install_dir = os.path.join(out_dir, "target", "product", product.product)
zip_sha_map = {} for img in _INSTALLED_IMG_FILES:
img_path = os.path.join(install_dir, img) # Some devices do not build partitions like dtbo.img # Skip if .img file is not found in install dir. if os.path.exists(img_path):
zip_sha_map[img] = get_installed_img_sha(img_path)
return zip_sha_map
def compare_installed_img_sha_maps(product: Product, soong_only_map: dict[str, str], soong_plus_make_map: dict[str, str]) -> bool: """Compares two sha maps of installed .img files and reports any missing or different entries."""
all_keys = sorted(list(soong_only_map.keys() | soong_plus_make_map.keys()))
all_identical = True # Append diffs to report. with open(get_comparison_report_path(product), 'at') as file: for key in all_keys:
allowlisted = key in _INSTALLED_IMG_FILES_SHA_DIFF_ALLOWLIST
allowlisted_str = "ALLOWLISTED"if allowlisted else"NOT ALLOWLISTED" if key notin soong_only_map:
print(f'$ANDROID_PRODUCT_OUT/{key} not found in soong only droid builds ({allowlisted_str})', file=file)
all_identical = all_identical and allowlisted elif key notin soong_plus_make_map:
print(f'$ANDROID_PRODUCT_OUT/{key} not found in soong plus make droid builds ({allowlisted_str})', file=file)
all_identical = all_identical and allowlisted elif soong_only_map[key] != soong_plus_make_map[key]:
print(f'$ANDROID_PRODUCT_OUT/{key} sha value differ between soong only build and soong plus make build ({allowlisted_str})', file=file)
all_identical = all_identical and allowlisted
return all_identical
_DIST_FILES = [ "super.img",
]
_DIST_FILES_ALLOWLIST = [
]
def get_dist_img_sha_map(product: Product) -> dict[str, str]: """Returns the map of dist file its SHA256 value."""
out_dir = os.getenv('OUT_DIR', 'out')
dist_dir = os.getenv('DIST_DIR', os.path.join(out_dir, 'dist'))
zip_sha_map = {} for file in _DIST_FILES:
file_path = os.path.join(dist_dir, file) # Some devices do not dist super.img # Skip if .img file is not found in dist dir. if os.path.exists(file_path):
zip_sha_map[file] = get_installed_img_sha(file_path)
return zip_sha_map
def compare_dist_file_sha_maps(product: Product, soong_only_map: dict[str, str], soong_plus_make_map: dict[str, str]) -> bool: """Compares two sha maps of dist files and reports any missing or different entries."""
all_keys = sorted(list(soong_only_map.keys() | soong_plus_make_map.keys()))
all_identical = True # Append diffs to report. with open(get_comparison_report_path(product), 'at') as file: for key in all_keys:
allowlisted = key in _DIST_FILES_ALLOWLIST
allowlisted_str = "ALLOWLISTED"if allowlisted else"NOT ALLOWLISTED" if key notin soong_only_map:
print(f'$out/dist/{key} not found in soong only droid builds ({allowlisted_str})', file=file)
all_identical = all_identical and allowlisted elif key notin soong_plus_make_map:
print(f'$out/dist/{key} not found in soong plus make droid builds ({allowlisted_str})', file=file)
all_identical = all_identical and allowlisted elif soong_only_map[key] != soong_plus_make_map[key]:
print(f'$out/dist/{key} sha value differ between soong only build and soong plus make build ({allowlisted_str})', file=file)
all_identical = all_identical and allowlisted
return all_identical
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument("products", nargs='+', help="one or more target product names")
parser.add_argument("--release", "-r", default="trunk_staging", help="the release to build for") return parser.parse_args()
def main():
os.chdir(get_top())
args = parse_args()
products = [
Product(
p,
args.release, 'userdebug',
) for p in args.products
]
if soong_only_zip_sha_map and soong_plus_make_zip_sha_map: ifnot compare_sha_maps(product, soong_only_zip_sha_map, soong_plus_make_zip_sha_map):
target_files_differ_products.append(product)
if soong_only_installed_img_sha_map and soong_plus_make_installed_img_sha_map: ifnot compare_installed_img_sha_maps(product, soong_only_installed_img_sha_map, soong_plus_make_installed_img_sha_map):
target_files_differ_products.append(product)
if soong_only_dist_file_sha_map and soong_plus_make_dist_file_sha_map: ifnot compare_dist_file_sha_maps(product, soong_only_dist_file_sha_map, soong_plus_make_dist_file_sha_map):
target_files_differ_products.append(product)
print(f"Diff test for {product.product} completed.")
for p in soong_plus_make_build_failed_products:
print(f"{p.product}: soong+make build failed. Please check out/soong.1.log and out/verbose.log.1.gz", file=sys.stderr) for p in soong_only_build_failed_products:
print(f"{p.product}: soong-only build failed. Please check out/soong.log and out/verbose.log.gz", file=sys.stderr) for p in target_files_differ_products:
print(f"{p.product}: target-file.zip and/or $ANDROID_PRODUCT_OUT differs", file=sys.stderr)
if len(products) == 1:
report = get_comparison_report_path(products[0]) if os.path.isfile(report): with open(report) as f:
print(f.read(), file=sys.stderr)
if soong_plus_make_build_failed_products or soong_only_build_failed_products or target_files_differ_products:
sys.exit(1) else:
print("target_files.zip are identical between soong only build and soong plus make build")
if __name__ == "__main__":
main()
Messung V0.5 in Prozent
¤ Dauer der Verarbeitung: 0.20 Sekunden
(vorverarbeitet am 2026-06-28)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.