1#!/bin/bash
2#
3#  Copyright (C) 2019 The Android Open Source Project
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
19supportRoot="$(cd $(dirname $0)/.. && pwd)"
20checkoutRoot="$(cd ${supportRoot}/../.. && pwd)"
21
22function die() {
23  echo "$@" >&2
24  exit 1
25}
26
27function usage() {
28  violation="$1"
29  die "
30  Usage: $0 <git treeish>
31         $0 <path>:<git treeish> <path>:<git treeish>
32
33  Validates that libraries built from the given versions are the same as
34  the build outputs built at HEAD. This can be used to validate that a refactor
35  did not change the outputs.
36  If a git treeish is given with no path, the path is considered to be frameworks/support
37
38  Example: $0 HEAD^
39  Example: $0 prebuilts/androidx/external:HEAD^ frameworks/support:work^
40
41    * A git treeish is what you type when you run 'git checkout <git treeish>'
42    See also https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddeftree-ishatree-ishalsotreeish .
43
44  You can also supply additional arguments that will be passed through to validateRefactorHelper.py, using -P
45  For example, the baseline arguments that validateRefactorHelper.py accepts.
46  Example: $0 HEAD^ -p agpKmp
47
48  validateRefactor also accepts git treeishes as named arguments using -g
49  Example: $0 -g HEAD^ -p agpKmp
50  "
51  return 1
52}
53
54# Fills in a default repository path of "frameworks/support:" for any args that don't specify
55# their repository. Given an input of: "work^ prebuilts/androidx/external:HEAD^", should return
56# "frameworks/support:work^ prebuilts/androidx/external:HEAD^".
57function expandCommitArgs() {
58  inputSpecs="$@"
59  outputSpecs=""
60  for spec in $inputSpecs; do
61    if echo "$spec" | grep -v ":" >/dev/null; then
62      spec="frameworks/support:$spec"
63    fi
64    outputSpecs="$outputSpecs $spec"
65  done
66  echo $outputSpecs
67}
68
69# Given a list of paths like "frameworks/support prebuilts/androidx/external",
70# runs `git checkout -` in each
71function uncheckout() {
72  repositoryPaths="$@"
73  for repositoryPath in $repositoryPaths; do
74    echoAndDo git -C "$checkoutRoot/$repositoryPath" checkout -
75  done
76}
77# Given a list of version specs like "a/b:c d/e:f", returns just the paths: "a/b d/e"
78function getParticipatingProjectPaths() {
79  specs="$@"
80  result=""
81  for arg in $specs; do
82    echo parsing $arg >&2
83    repositoryPath="$(echo $arg | sed 's|\([^:]*\):\([^:]*\)|\1|')"
84    otherVersion="$(echo $arg | sed 's|\([^:]*\):\([^:]*\)|\2|')"
85    if [ "$otherVersion" != "HEAD" ]; then
86      result="$result $repositoryPath"
87    fi
88  done
89  echo $result
90}
91# Given a list of paths, returns a string containing the currently checked-out version of each
92function getCurrentCommits() {
93  repositoryPaths="$@"
94  result=""
95  for repositoryPath in $repositoryPaths; do
96    currentVersion="$(cd $checkoutRoot/$repositoryPath && git log -1 --format=%H)"
97    result="$result $repositoryPath:$currentVersion"
98  done
99  echo $result
100}
101function echoAndDo() {
102  echo "$*"
103  eval "$*"
104}
105# Given a list of version specs like "a/b:c d/e:f", checks out the appropriate version in each
106# In this example it would be `cd a/b && git checkout e` and `cd e/e && git checkout f`
107function checkout() {
108  versionSpecs="$1"
109  for versionSpec in $versionSpecs; do
110    project="$(echo $versionSpec | sed 's|\([^:]*\):\([^:]*\)|\1|')"
111    ref="$(echo     $versionSpec | sed 's|\([^:]*\):\([^:]*\)|\2|')"
112    echo "checking out $ref in project $project"
113    echoAndDo git -C "$checkoutRoot/$project" checkout "$ref"
114  done
115}
116function unzipInPlace() {
117  archiveName="$1"
118  echoAndDo unzip -q "$archiveName" -d "${archiveName}.unzipped"
119}
120function doBuild() {
121  # build androidx
122  echoAndDo ./gradlew createAllArchives zipDocs --no-daemon --rerun-tasks --offline -Pandroidx.highMemory -Pandroidx.constraints=true
123  archiveName="top-of-tree-m2repository-all-0.zip"
124  unzipInPlace "${tempOutPath}/dist/top-of-tree-m2repository-all-0.zip"
125  unzipInPlace "${tempOutPath}/dist/docs-tip-of-tree-0.zip"
126  unzipInPlace "${tempOutPath}/dist/docs-public-0.zip"
127}
128
129nonNamedArgs=()
130oldCommits=()
131passThruArgs=()
132while [ $OPTIND -le "$#" ]; do
133  if getopts ":p:g:" opt; then
134    case $opt in
135      \? ) usage;;
136      g ) oldCommits+="$(expandCommitArgs $OPTARG)";;
137      p ) passThruArgs+="$OPTARG";;
138    esac
139    case $OPTARG in
140      -*) usage;;
141    esac
142  else
143    nonNamedArgs+=("${!OPTIND}")
144    ((OPTIND++))
145  fi
146done
147
148oldCommits+="$(expandCommitArgs $nonNamedArgs)"
149
150projectPaths="$(getParticipatingProjectPaths $oldCommits)"
151if [ "$oldCommits" == "" ]; then
152  usage
153fi
154
155newCommits="$(getCurrentCommits $projectPaths)"
156cd "$supportRoot"
157if [[ $(git update-index --refresh) ]]; then echo "You have local changes; stash or commit them or this script won't work"; exit 1; fi
158if [[ $(git diff-index --quiet HEAD) ]]; then echo "You have local changes; stash or commit them or this script won't work"; exit 1; fi
159echo old commits: $oldCommits
160echo new commits: $newCommits
161cd "$supportRoot"
162oldOutPath="${checkoutRoot}/out-old"
163newOutPath="${checkoutRoot}/out-new"
164tempOutPath="${checkoutRoot}/out"
165
166rm -rf "$oldOutPath" "$newOutPath" "$tempOutPath"
167
168echo building new commit
169doBuild
170mv "$tempOutPath" "$newOutPath"
171
172echo building previous commit
173
174checkout "$oldCommits"
175if doBuild; then
176  echo previous build succeeded
177else
178  echo previous build failed
179  uncheckout "$projectPaths"
180  exit 1
181fi
182uncheckout "$projectPaths"
183mv "$tempOutPath" "$oldOutPath"
184
185
186echo
187echo diffing results
188# This script performs the diff, and filters out known issues and non-issues with baselines
189python3 development/validateRefactorHelper.py "$passThruArgs"
190echo end of difference
191