#! /bin/bash -eu # Compares two APEX files. # This script is aimed at regression testing. It allows to compare an # APEX target built by Bazel to the same target built by Soong. # The first of its arguments is the reference APEX (the one built by # Soong), the second is "our" APEX (built by Bazel). # # An APEX is a ZIP archive, so we treat each APEX as a file system and # compare these two file systems. The script displays: # - missing files (those in the reference APEX missing from our APEX) # - extra files (those only in our APEX) # - for each file present in both, their difference. # The main part of an APEX is an image file (payload.img), which is an # image of a filesystem in EXT2 format. The script "mounts" such image # and then compares them side by side. # # This script relies on the presence of an executable (binary/script) # to "mount" a file of certain formats as file systems. It runs this # executable as follows: # * mount ZIPFILE at DIR: # view_file_as_fs zip ZIPFILE DIR # * unmount ZIPFILE at DIR: # view_file_as_fs -u zip DIR # * mount EXT2 image IMGFILE at DIR: # view_file_as_fs ext2 IMGFILE DIR # * unmount EXT2 image IMGFILE at DIR: # view_file_as_fs -u ext2 DIR # function die() { format=$1; shift; printf "$format\n" $@; exit 1; } # Delouse (($# == 2)) || die "usage: ${0##*/} REF_APEX OUR_APEX" declare -r ref_apex=$1 our_apex=$2 for f in $ref_apex $our_apex; do [[ -f $f ]] || die "$f does not exist" done # Maybe we are lucky. cmp -s $ref_apex $our_apex && exit declare -r file_as_fs_viewer=$(which view_file_as_fs) if [[ -z "${file_as_fs_viewer}" ]]; then cat <<"EOF" You need to have file-as-filesystem viewer application `view_file_as_fs` on the PATH. If you have FUSE's fuse-ext2 and fuse-zip installed, you can the following script below view_file_as_fs: #!/bin/bash -eu # # Mounts a file as a read-only filesystem or unmounts such previously # mounted file system. # This script can mount a zip file or an file containing an ext2 image # as a file system. It requires the presence of fuse-zip and fuse-ext2 # FUSE packages. function die() { format=$1; shift; printf "$format\n" $@; exit 1; } function usage() { die "Usage:\n ${0##*/} {ext2|zip} FILE MOUNT-POINT\nor\n ${0##*/} -u {ext2|zip} MOUNT-POINT" } declare umount= while getopts "u" opt; do case $opt in u) umount=t ;; ?) usage esac done shift $(($OPTIND-1)) if [[ -n "$umount" ]]; then (($#==2)) || usage mount | grep -q "on $2 " && umount "$2" else (($#==3)) || usage declare -r file="$2" mt="$3" [[ -d "$mt" && -z "$(ls -1A $mt)" ]] || die "$mt should be an empty directory" case "$1" in ext2) fuse-ext2 "$file" "$mt" ;; zip) [[ -f $file ]] || die "$file is not a file" # Because fuse-zip silently mounts it as empty fuse-zip "$file" "$mt" ;; *) usage ;; esac fi EOF exit 1 fi # "Mounts" file as filesystem and prints the sorted list of files in it. function mount_and_list() { $file_as_fs_viewer $1 $2 $3 2>/dev/null find $3 -type f -printf "%P\n" } function cleanup() { for d in $fuse_dir/*.img; do $file_as_fs_viewer -u ext2 $d || /bin/true done for d in $fuse_dir/*.apex; do $file_as_fs_viewer -u zip $d || /bin/true done rm -rf $fuse_dir } function dump_proto() { protoc --decode $1 $2 } function dump_buildinfo() { dump_proto apex.proto.ApexBuildInfo system/apex/proto/apex_build_info.proto } function dump_apex_manifest() { dump_proto apex.proto.ApexManifest system/apex/proto/apex_manifest.proto } function compare_images() { local -r ref_img=$1 our_img=$2 # Mount each APEX and save its sorted contents. Classify the contents mount_and_list ext2 $ref_img $fuse_dir/ref.img >$fuse_dir/ref.img.list mount_and_list ext2 $our_img $fuse_dir/our.img >$fuse_dir/our.img.list . <(classify $fuse_dir/ref.img.list $fuse_dir/our.img.list; /bin/true) # Now we have missing/extra/common holding respective file lists. Compare ((${#missing[@]}==0)) || \ { printf "Missing image files:"; printf " %s" ${missing[@]}; printf "\n"; } ((${#extra[@]}==0)) || \ { printf "Extra image files:"; printf " %s" ${extra[@]}; printf "\n"; } for f in "${common[@]}"; do cmp -s $fuse_dir/{ref,our}.img/$f && continue echo " $f" in image differs: case $f in etc/init.rc) diff $fuse_dir/{ref,our}.img/$f || /bin/true ;; apex_manifest.pb) diff <(dump_apex_manifest <$fuse_dir/ref.img/$f) <(dump_apex_manifest <$fuse_dir/our.img/$f) || bin/true ;; *) # TODO: should do more than just size comparison. sizes=($(stat --format "%s" $fuse_dir/{ref,our}.img/$f)) delta=$((${sizes[1]}-${sizes[0]})) (($delta==0)) || printf " size differs: %d (%d)\n" ${sizes[1]} $delta ;; esac done } # Prints the script that sets `missing`/`extra`/`common` shell # variable to an array containing corresponding files, i.e. its # output is # declare declare -a missing=() extra=() common=() # missing+=(missing_file) # extra+=(extra_file) # common+=(common_file) # ..... function classify() { comm $1 $2 | sed -nr \ -e '1ideclare -a missing=() extra=() common=()' \ -e '/^\t\t/{s/\t\t(.*)/common+=(\1)/p;d}' \ -e '/^\t/{s/^\t(.*)/extra+=(\1)/p;d}' \ -e 's/(.*)/missing+=(\1)/p'; /bin/true } fuse_dir=$(mktemp -d --tmpdir apexfuse.XXXXX) mkdir -p $fuse_dir/{our,ref}.{apex,img} trap cleanup EXIT # Mount each APEX and save its sorted contents. Classify the contents mount_and_list zip $ref_apex $fuse_dir/ref.apex >$fuse_dir/ref.apex.list mount_and_list zip $our_apex $fuse_dir/our.apex >$fuse_dir/our.apex.list . <(classify $fuse_dir/ref.apex.list $fuse_dir/our.apex.list; /bin/true) # Now we have missing/extra/common holding respective file lists. Compare ((${#missing[@]}==0)) || { printf "Missing files:"; printf " %s" ${missing[@]}; printf "\n"; } ((${#extra[@]}==0)) || { printf "Extra files:"; printf " %s" ${extra[@]}; printf "\n"; } for f in "${common[@]}"; do cmp -s $fuse_dir/{ref,our}.apex/$f && continue # File differs, compare known file types intelligently case $f in AndroidManifest.xml) echo $f differs: diff \ <(aapt dump xmltree $fuse_dir/ref.apex AndroidManifest.xml) \ <(aapt dump xmltree $fuse_dir/our.apex AndroidManifest.xml) || /bin/true ;; apex_build_info.pb) echo $f differs: diff <(dump_buildinfo <$fuse_dir/ref.apex/$f) <(dump_buildinfo <$fuse_dir/our.apex/$f) || /bin/true ;; manifest.pb) echo $f differs: diff <(dump_apex_manifest <$fuse_dir/ref.apex/$f) <(dump_apex_manifest <$fuse_dir/our.apex/$f) || bin/true ;; apex_payload.img) echo image $f differs, mounting it: compare_images $fuse_dir/{ref,our}.apex/$f ;; META-INF/*) # Ignore these. They are derived from the rest # showing their difference does not help. ;; *) echo $f; diff $fuse_dir/{ref,our}.apex/$f || /bin/true esac done