1#!/bin/bash -eu
2#
3# Copyright 2017 Google Inc. All rights reserved.
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#     http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17set -e
18
19# This file makes it easy to confirm that a set of changes in source code don't result in any
20# changes to the generated ninja files. This is to reduce the effort required to be confident
21# in the correctness of refactorings
22
23function die() {
24  echo "$@" >&2
25  exit 1
26}
27
28function usage() {
29  violation="$1"
30  die "$violation
31
32  Usage: diff_build_graphs.sh [--products=product1,product2...] <OLD_VERSIONS> <NEW_VERSIONS>
33
34  This file builds and parses the build files (Android.mk, Android.bp, etc) for each requested
35  product and for both sets of versions, and checks whether the ninja files (which implement
36  the build graph) changed between the two versions.
37
38  Example: diff_build_graphs.sh 'build/soong:work^ build/blueprint:work^' 'build/soong:work build/blueprint:work'
39
40  Options:
41    --products=PRODUCTS  comma-separated list of products to check"
42}
43
44PRODUCTS_ARG=""
45OLD_VERSIONS=""
46NEW_VERSIONS=""
47function parse_args() {
48  # parse optional arguments
49  while true; do
50    arg="${1-}"
51    case "$arg" in
52      --products=*) PRODUCTS_ARG="$arg";;
53      *) break;;
54    esac
55    shift
56  done
57  # parse required arguments
58  if [ "$#" != "2" ]; then
59    usage ""
60  fi
61  #argument validation
62  OLD_VERSIONS="$1"
63  NEW_VERSIONS="$2"
64
65}
66parse_args "$@"
67
68
69# find some file paths
70cd "$(dirname $0)"
71SCRIPT_DIR="$PWD"
72cd ../../..
73CHECKOUT_ROOT="$PWD"
74OUT_DIR="${OUT_DIR-}"
75if [ -z "$OUT_DIR" ]; then
76  OUT_DIR=out
77fi
78WORK_DIR="$OUT_DIR/diff"
79OUT_DIR_OLD="$WORK_DIR/out_old"
80OUT_DIR_NEW="$WORK_DIR/out_new"
81OUT_DIR_TEMP="$WORK_DIR/out_temp"
82
83
84function checkout() {
85  versionSpecs="$1"
86  for versionSpec in $versionSpecs; do
87    project="$(echo $versionSpec | sed 's|\([^:]*\):\([^:]*\)|\1|')"
88    ref="$(echo     $versionSpec | sed 's|\([^:]*\):\([^:]*\)|\2|')"
89    echo "checking out ref $ref in project $project"
90    git -C "$project" checkout "$ref"
91  done
92}
93
94function run_build() {
95  echo
96  echo "Starting build"
97  # rebuild multiproduct_kati, in case it was missing before,
98  # or in case it is affected by some of the changes we're testing
99  make blueprint_tools
100  # find multiproduct_kati and have it build the ninja files for each product
101  builder="$(echo $OUT_DIR/soong/host/*/bin/multiproduct_kati)"
102  BUILD_NUMBER=sample "$builder" $PRODUCTS_ARG --keep --out "$OUT_DIR_TEMP" || true
103  echo
104}
105
106function diffProduct() {
107  product="$1"
108
109  zip1="$OUT_DIR_OLD/${product}.zip"
110  unzipped1="$OUT_DIR_OLD/$product"
111
112  zip2="$OUT_DIR_NEW/${product}.zip"
113  unzipped2="$OUT_DIR_NEW/$product"
114
115  unzip -qq "$zip1" -d "$unzipped1"
116  unzip -qq "$zip2" -d "$unzipped2"
117
118  #do a diff of the ninja files
119  diffFile="$WORK_DIR/diff.txt"
120  diff -r "$unzipped1" "$unzipped2" -x build_date.txt -x build_number.txt -x '\.*' -x '*.log' -x build_fingerprint.txt -x build.ninja.d -x '*.zip' > $diffFile || true
121  if [[ -s "$diffFile" ]]; then
122    # outputs are different, so remove the unzipped versions but keep the zipped versions
123    echo "First few differences (total diff linecount=$(wc -l $diffFile)) for product $product:"
124    cat "$diffFile" | head -n 10
125    echo "End of differences for product $product"
126    rm -rf "$unzipped1" "$unzipped2"
127  else
128    # outputs are the same, so remove all of the outputs
129    rm -rf "$zip1" "$unzipped1" "$zip2" "$unzipped2"
130  fi
131}
132
133function do_builds() {
134  #reset work dir
135  rm -rf "$WORK_DIR"
136  mkdir "$WORK_DIR"
137
138  #build new code
139  checkout "$NEW_VERSIONS"
140  run_build
141  mv "$OUT_DIR_TEMP" "$OUT_DIR_NEW"
142
143  #build old code
144  #TODO do we want to cache old results? Maybe by the time we care to cache old results this will
145  #be running on a remote server somewhere and be completely different
146  checkout "$OLD_VERSIONS"
147  run_build
148  mv "$OUT_DIR_TEMP" "$OUT_DIR_OLD"
149
150  #cleanup
151  echo created "$OUT_DIR_OLD" and "$OUT_DIR_NEW"
152}
153
154function main() {
155  do_builds
156  checkout "$NEW_VERSIONS"
157
158  #find all products
159  productsFile="$WORK_DIR/all_products.txt"
160  find $OUT_DIR_OLD $OUT_DIR_NEW -mindepth 1 -maxdepth 1 -name "*.zip" | sed "s|^$OUT_DIR_OLD/||" | sed "s|^$OUT_DIR_NEW/||" | sed "s|\.zip$||" | sort | uniq > "$productsFile"
161  echo Diffing products
162  for product in $(cat $productsFile); do
163    diffProduct "$product"
164  done
165  echo Done diffing products
166  echo "Any differing outputs can be seen at $OUT_DIR_OLD/*.zip and $OUT_DIR_NEW/*.zip"
167  echo "See $WORK_DIR/diff.txt for the full list of differences for the latest product checked"
168}
169
170main
171