Eine aufbereitete Darstellung der Quelle

 
     
 
 
Anforderungen  |   Konzepte  |   Entwurf  |   Entwicklung  |   Qualitätssicherung  |   Lebenszyklus  |   Steuerung
 
 
 
 

Benutzer

Quelle  soong_only_diff_test.py

  Sprache: Python
 

#!/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')

    if not 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:
                return False

        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:
                return False

        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)

        if result.returncode != 0:
            return False

        return True

    succeeded = inner()
    move_artifacts_to_subfolder(product, soong_only)
    return succeeded

# 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]:
    if not os.path.exists(zip_filepath):
        print(f"Error: File not found at {zip_filepath}", file=sys.stderr)
        return None

    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]

                        # Check internal signature
                        internal_sig = struct.unpack('<H', data_bytes[0:2])[0]
                        if internal_sig == SHA_256_HEADER_SIGNATURE:
                            found_sha_in_file = data_bytes[2:]
                            break

                i += (4 + block_data_size)

            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)

    return sha256_checksums

def find_build_id() -> str | None:
    tag_file_path = os.path.join(os.getenv('OUT_DIR''out'), 'file_name_tag.txt')
    build_id = None

    with open(tag_file_path, 'r', encoding='utf-8'as f:
            build_id = f.read().strip()

    return build_id


def get_sub_dist_dir(product: Product, soong_only: bool = None) -> str:
    subdir = os.path.join('soong_only_diffs', product.product)
    if soong_only is not None:
        subdir = os.path.join(subdir, "soong_only" if soong_only else "soong_plus_make")

    out_dir = os.getenv('OUT_DIR''out')
    dist_dir = os.getenv('DIST_DIR', os.path.join(out_dir, 'dist'))
    subdistdir = os.path.join(dist_dir, subdir)
    return subdistdir


def zip_ninja_files(subdistdir: str, product: Product):
    out_dir = os.getenv('OUT_DIR''out')
    root_dir = os.path.dirname(out_dir)
    root_ninja_name = f'combined-{product.product}.ninja'
    if is_env_true('EMMA_INSTRUMENT'):
        root_ninja_name = f'combined-{product.product}.coverage.ninja'
    root_ninja_name = os.path.join(out_dir, root_ninja_name)
    if not os.path.isfile(root_ninja_name):
        return
    files_to_zip = transitively_included_ninja_files(out_dir, root_ninja_name, {})

    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 move_artifacts_to_subfolder(product: Product, soong_only: bool):
    out_dir = os.getenv('OUT_DIR''out')
    dist_dir = os.getenv('DIST_DIR', os.path.join(out_dir, 'dist'))
    subdistdir = get_sub_dist_dir(product, soong_only)
    if os.path.exists(subdistdir):
        shutil.rmtree(subdistdir)
    os.makedirs(subdistdir)
    zip_ninja_files(subdistdir, product)

    build_id = find_build_id()

    files_to_move = [
        os.path.join(dist_dir, f'{product.product}-target_files-{build_id}.zip'), # target_files.zip
        os.path.join(out_dir, 'build.log'),
    ]

    for file in files_to_move:
        if os.path.isfile(file):
            shutil.move(file, subdistdir)

SHA_DIFF_ALLOWLIST = {
    "IMAGES/system.img",
    "IMAGES/userdata.img",
    "IMAGES/vbmeta_system.img",
    "META/apkcerts.txt",
    "META/misc_info.txt",
    "META/vbmeta_digest.txt",
}

def get_comparison_report_path(product: Product):
    return os.path.join(get_sub_dist_dir(product), 'comparison_report.txt')

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 not in 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 not in 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 is None:
        sys.exit("Could not construct sha map for target_files.zip entries for soong only build")

    return zip_sha_map

_INSTALLED_IMG_FILES = [
    "boot.img",
    "bootloader.img",
    "dtbo.img",
    "product.img",
    "pvmfw.img",
    "ramdisk.img",
    "super.img",
    "system_dlkm.img",
    "system_ext.img",
    "system_other.img",
    "system.img",
    "userdata.img",
    "vbmeta.img",
    "vbmeta_system.img",
    "vbmeta_vendor.img",
    "vendor_boot.img",
    "vendor_dlkm.img",
    "vendor.img",
    "vendor_kernel_boot.img",
    "vendor_ramdisk.img",
]

# 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 not in 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 not in 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 not in 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 not in 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
    ]

    target_files_differ_products = []
    soong_only_build_failed_products = []
    soong_plus_make_build_failed_products = []
    for product in products:
        soong_only = True
        soong_only_success = run_build_target_files_zip(product, soong_only)
        soong_only_zip_sha_map = None
        soong_only_installed_img_sha_map = None
        soong_only_dist_file_sha_map = None
        if soong_only_success:
            soong_only_zip_sha_map = get_zip_sha_map(product, soong_only)
            soong_only_installed_img_sha_map = get_installed_img_sha_map(product)
            soong_only_dist_file_sha_map = get_dist_img_sha_map(product)
        else:
            soong_only_build_failed_products.append(product)

        soong_only = False
        soong_plus_make_success = run_build_target_files_zip(product, soong_only)
        soong_plus_make_zip_sha_map = None
        soong_plus_make_installed_img_sha_map = None
        soong_plus_make_dist_file_sha_map = None
        if soong_plus_make_success:
            soong_plus_make_zip_sha_map = get_zip_sha_map(product, soong_only)
            soong_plus_make_installed_img_sha_map = get_installed_img_sha_map(product)
            soong_plus_make_dist_file_sha_map = get_dist_img_sha_map(product)
        else:
            soong_plus_make_build_failed_products.append(product)

        if soong_only_zip_sha_map and soong_plus_make_zip_sha_map:
            if not 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:
            if not 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:
            if not 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
C=86 H=91 G=88

¤ Dauer der Verarbeitung: 0.21 Sekunden  (vorverarbeitet am  2026-06-28) ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

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.






                                                                                                                                                                                                                                                                                                                                                                                                     


Neuigkeiten

     Aktuelles
     Motto des Tages

Software

     Quellcodebibliothek
     Eigene Quellcodes
     Fremde Quellcodes
     Suchen

Aktivitäten

     Artikel über Sicherheit
     Anleitung zur Aktivierung von SSL

Muße

     Gedichte
     Musik
     Bilder

Jenseits des Üblichen ....
    

Besucherstatistik

Besucherstatistik