1#! /bin/bash -eu 2 3# Compares two APEX files. 4# This script is aimed at regression testing. It allows to compare an 5# APEX target built by Bazel to the same target built by Soong. 6# The first of its arguments is the reference APEX (the one built by 7# Soong), the second is "our" APEX (built by Bazel). 8# 9# An APEX is a ZIP archive, so we treat each APEX as a file system and 10# compare these two file systems. The script displays: 11# - missing files (those in the reference APEX missing from our APEX) 12# - extra files (those only in our APEX) 13# - for each file present in both, their difference. 14# The main part of an APEX is an image file (payload.img), which is an 15# image of a filesystem in EXT2 format. The script "mounts" such image 16# and then compares them side by side. 17# 18# This script relies on the presence of an executable (binary/script) 19# to "mount" a file of certain formats as file systems. It runs this 20# executable as follows: 21# * mount ZIPFILE at DIR: 22# view_file_as_fs zip ZIPFILE DIR 23# * unmount ZIPFILE at DIR: 24# view_file_as_fs -u zip DIR 25# * mount EXT2 image IMGFILE at DIR: 26# view_file_as_fs ext2 IMGFILE DIR 27# * unmount EXT2 image IMGFILE at DIR: 28# view_file_as_fs -u ext2 DIR 29# 30 31function die() { format=$1; shift; printf "$format\n" $@; exit 1; } 32 33# Delouse 34(($# == 2)) || die "usage: ${0##*/} REF_APEX OUR_APEX" 35declare -r ref_apex=$1 our_apex=$2 36for f in $ref_apex $our_apex; do 37 [[ -f $f ]] || die "$f does not exist" 38done 39 40# Maybe we are lucky. 41cmp -s $ref_apex $our_apex && exit 42 43declare -r file_as_fs_viewer=$(which view_file_as_fs) 44if [[ -z "${file_as_fs_viewer}" ]]; then 45 cat <<"EOF" 46You need to have file-as-filesystem viewer application `view_file_as_fs` 47on the PATH. If you have FUSE's fuse-ext2 and fuse-zip installed, you 48can the following script below view_file_as_fs: 49 50#!/bin/bash -eu 51# 52# Mounts a file as a read-only filesystem or unmounts such previously 53# mounted file system. 54# This script can mount a zip file or an file containing an ext2 image 55# as a file system. It requires the presence of fuse-zip and fuse-ext2 56# FUSE packages. 57function die() { format=$1; shift; printf "$format\n" $@; exit 1; } 58function usage() { 59 die "Usage:\n ${0##*/} {ext2|zip} FILE MOUNT-POINT\nor\n ${0##*/} -u {ext2|zip} MOUNT-POINT" 60} 61 62declare umount= 63while getopts "u" opt; do 64 case $opt in 65 u) umount=t ;; 66 ?) usage 67 esac 68done 69 70shift $(($OPTIND-1)) 71if [[ -n "$umount" ]]; then 72 (($#==2)) || usage 73 mount | grep -q "on $2 " && umount "$2" 74else 75 (($#==3)) || usage 76 declare -r file="$2" mt="$3" 77 [[ -d "$mt" && -z "$(ls -1A $mt)" ]] || die "$mt should be an empty directory" 78 case "$1" in 79 ext2) fuse-ext2 "$file" "$mt" ;; 80 zip) 81 [[ -f $file ]] || die "$file is not a file" # Because fuse-zip silently mounts it as empty 82 fuse-zip "$file" "$mt" ;; 83 *) usage ;; 84 esac 85fi 86EOF 87 exit 1 88fi 89 90# "Mounts" file as filesystem and prints the sorted list of files in it. 91function mount_and_list() { 92 $file_as_fs_viewer $1 $2 $3 2>/dev/null 93 find $3 -type f -printf "%P\n" 94} 95 96function cleanup() { 97 for d in $fuse_dir/*.img; do 98 $file_as_fs_viewer -u ext2 $d || /bin/true 99 done 100 for d in $fuse_dir/*.apex; do 101 $file_as_fs_viewer -u zip $d || /bin/true 102 done 103 rm -rf $fuse_dir 104} 105 106function dump_proto() { 107 protoc --decode $1 $2 108} 109 110function dump_buildinfo() { 111 dump_proto apex.proto.ApexBuildInfo system/apex/proto/apex_build_info.proto 112} 113 114function dump_apex_manifest() { 115 dump_proto apex.proto.ApexManifest system/apex/proto/apex_manifest.proto 116} 117 118function compare_images() { 119 local -r ref_img=$1 our_img=$2 120 121 # Mount each APEX and save its sorted contents. Classify the contents 122 mount_and_list ext2 $ref_img $fuse_dir/ref.img >$fuse_dir/ref.img.list 123 mount_and_list ext2 $our_img $fuse_dir/our.img >$fuse_dir/our.img.list 124 . <(classify $fuse_dir/ref.img.list $fuse_dir/our.img.list; /bin/true) 125 126 # Now we have missing/extra/common holding respective file lists. Compare 127 ((${#missing[@]}==0)) || \ 128 { printf "Missing image files:"; printf " %s" ${missing[@]}; printf "\n"; } 129 ((${#extra[@]}==0)) || \ 130 { printf "Extra image files:"; printf " %s" ${extra[@]}; printf "\n"; } 131 for f in "${common[@]}"; do 132 cmp -s $fuse_dir/{ref,our}.img/$f && continue 133 echo " $f" in image differs: 134 case $f in 135 etc/init.rc) 136 diff $fuse_dir/{ref,our}.img/$f || /bin/true 137 ;; 138 apex_manifest.pb) 139 diff <(dump_apex_manifest <$fuse_dir/ref.img/$f) <(dump_apex_manifest <$fuse_dir/our.img/$f) || bin/true 140 ;; 141 *) 142 # TODO: should do more than just size comparison. 143 sizes=($(stat --format "%s" $fuse_dir/{ref,our}.img/$f)) 144 delta=$((${sizes[1]}-${sizes[0]})) 145 (($delta==0)) || printf " size differs: %d (%d)\n" ${sizes[1]} $delta 146 ;; 147 esac 148 done 149} 150 151# Prints the script that sets `missing`/`extra`/`common` shell 152# variable to an array containing corresponding files, i.e. its 153# output is 154# declare declare -a missing=() extra=() common=() 155# missing+=(missing_file) 156# extra+=(extra_file) 157# common+=(common_file) 158# ..... 159function classify() { 160 comm $1 $2 | sed -nr \ 161 -e '1ideclare -a missing=() extra=() common=()' \ 162 -e '/^\t\t/{s/\t\t(.*)/common+=(\1)/p;d}' \ 163 -e '/^\t/{s/^\t(.*)/extra+=(\1)/p;d}' \ 164 -e 's/(.*)/missing+=(\1)/p'; /bin/true 165} 166 167fuse_dir=$(mktemp -d --tmpdir apexfuse.XXXXX) 168mkdir -p $fuse_dir/{our,ref}.{apex,img} 169trap cleanup EXIT 170 171# Mount each APEX and save its sorted contents. Classify the contents 172mount_and_list zip $ref_apex $fuse_dir/ref.apex >$fuse_dir/ref.apex.list 173mount_and_list zip $our_apex $fuse_dir/our.apex >$fuse_dir/our.apex.list 174. <(classify $fuse_dir/ref.apex.list $fuse_dir/our.apex.list; /bin/true) 175 176# Now we have missing/extra/common holding respective file lists. Compare 177((${#missing[@]}==0)) || { printf "Missing files:"; printf " %s" ${missing[@]}; printf "\n"; } 178((${#extra[@]}==0)) || { printf "Extra files:"; printf " %s" ${extra[@]}; printf "\n"; } 179 180for f in "${common[@]}"; do 181 cmp -s $fuse_dir/{ref,our}.apex/$f && continue 182 # File differs, compare known file types intelligently 183 case $f in 184 AndroidManifest.xml) 185 echo $f differs: 186 diff \ 187 <(aapt dump xmltree $fuse_dir/ref.apex AndroidManifest.xml) \ 188 <(aapt dump xmltree $fuse_dir/our.apex AndroidManifest.xml) || /bin/true 189 ;; 190 apex_build_info.pb) 191 echo $f differs: 192 diff <(dump_buildinfo <$fuse_dir/ref.apex/$f) <(dump_buildinfo <$fuse_dir/our.apex/$f) || /bin/true 193 ;; 194 manifest.pb) 195 echo $f differs: 196 diff <(dump_apex_manifest <$fuse_dir/ref.apex/$f) <(dump_apex_manifest <$fuse_dir/our.apex/$f) || bin/true 197 ;; 198 apex_payload.img) 199 echo image $f differs, mounting it: 200 compare_images $fuse_dir/{ref,our}.apex/$f 201 ;; 202 META-INF/*) 203 # Ignore these. They are derived from the rest 204 # showing their difference does not help. 205 ;; 206 *) echo $f; diff $fuse_dir/{ref,our}.apex/$f || /bin/true 207 esac 208done 209