blob: bce19c8297e15e6844d51fc7fd2e3394a41005a4 [file] [log] [blame]
Jeff Gastoncc0993d2019-04-02 18:02:44 -04001#!/bin/bash
2set -e
3set -u
4
5scriptName="$(basename $0)"
6
7function usage() {
Jeff Gastona6c665042020-07-22 12:57:33 -04008 echo "NAME"
9 echo " diagnose-build-failure.sh"
Jeff Gastoncc0993d2019-04-02 18:02:44 -040010 echo
Jeff Gastona6c665042020-07-22 12:57:33 -040011 echo "SYNOPSIS"
12 echo " ./development/diagnose-build-failure/diagnose-build-failure.sh [--message <message>] '<tasks>'"
Jeff Gastoncc0993d2019-04-02 18:02:44 -040013 echo
Jeff Gastona6c665042020-07-22 12:57:33 -040014 echo "DESCRIPTION"
15 echo " Attempts to identify why "'`'"./gradlew <tasks>"'`'" fails"
16 echo
17 echo "OPTIONS"
18 echo "--message <message>"
19 echo " Replaces the requirement for "'`'"./gradlew <tasks>"'`'" to fail with the requirement that it produces the given message"
20 echo
21 echo "SAMPLE USAGE"
Jeff Gastoncc0993d2019-04-02 18:02:44 -040022 echo " $0 assembleDebug # or any other arguments you would normally give to ./gradlew"
23 echo
Jeff Gastona6c665042020-07-22 12:57:33 -040024 echo "OUTPUT"
25 echo " diagnose-build-failure will conclude one of the following:"
Jeff Gastoncc0993d2019-04-02 18:02:44 -040026 echo
27 echo " A) Some state saved in memory by the Gradle daemon is triggering an error"
28 echo " B) Your source files have been changed"
29 echo " C) Some file in the out/ dir is triggering an error"
30 echo " If this happens, $scriptName will identify which file(s) specifically"
31 echo " D) The build is nondeterministic and/or affected by timestamps"
32 echo " E) The build via gradlew actually passes"
33 exit 1
34}
35
Jeff Gastona6c665042020-07-22 12:57:33 -040036expectedMessage=""
37while true; do
38 if [ "$#" -lt 1 ]; then
39 usage
40 fi
41 arg="$1"
42 shift
43 if [ "$arg" == "--message" ]; then
44 expectedMessage="$1"
45 shift
46 continue
47 fi
48 gradleArgs="$arg"
49 break
50done
Jeff Gastoncc0993d2019-04-02 18:02:44 -040051if [ "$gradleArgs" == "" ]; then
52 usage
53fi
54
Jeff Gastona6c665042020-07-22 12:57:33 -040055if [ "$#" -gt 0 ]; then
56 echo "Unrecognized argument: $1"
57 exit 1
58fi
59
Jeff Gaston63234502019-07-09 13:47:31 -040060workingDir="$(pwd)"
61if [ ! -e "$workingDir/gradlew" ]; then
62 echo "Error; ./gradlew does not exist. Must cd to a dir containing a ./gradlew first"
63 # so that this script knows which gradlew to use (in frameworks/support or frameworks/support/ui)
64 exit 1
65fi
66
Jeff Gastoncc0993d2019-04-02 18:02:44 -040067scriptPath="$(cd $(dirname $0) && pwd)"
Jeff Gaston599b9e32020-08-05 18:36:56 -040068vgrep="$scriptPath/impl/vgrep.sh"
Jeff Gastoncc0993d2019-04-02 18:02:44 -040069supportRoot="$(cd $scriptPath/../.. && pwd)"
70checkoutRoot="$(cd $supportRoot/../.. && pwd)"
Jeff Gastona58e3082019-08-05 19:44:26 -040071tempDir="$checkoutRoot/diagnose-build-failure/"
Jeff Gastoncc0993d2019-04-02 18:02:44 -040072if [ "${GRADLE_USER_HOME:-}" == "" ]; then
73 GRADLE_USER_HOME="$(cd ~ && pwd)/.gradle"
74fi
75COLOR_WHITE="\e[97m"
76COLOR_GREEN="\e[32m"
77
78function checkStatusRepo() {
79 repo status
80}
81
82function checkStatusGit() {
83 git status
84 git log -1
85}
86
87function checkStatus() {
88 cd "$checkoutRoot"
89 if [ "-e" .repo ]; then
90 checkStatusRepo
91 else
92 checkStatusGit
93 fi
94}
95
Jeff Gaston599b9e32020-08-05 18:36:56 -040096function getBuildComand() {
Jeff Gastona6c665042020-07-22 12:57:33 -040097 if [ "$expectedMessage" == "" ]; then
Jeff Gastona6c665042020-07-22 12:57:33 -040098 testCommand="$*"
Jeff Gastona6c665042020-07-22 12:57:33 -040099 else
Jeff Gaston599b9e32020-08-05 18:36:56 -0400100 testCommand="$* 2>&1 | $vgrep '$expectedMessage'"
Jeff Gastona6c665042020-07-22 12:57:33 -0400101 fi
Jeff Gaston599b9e32020-08-05 18:36:56 -0400102 echo "$testCommand"
103}
104
105function runBuild() {
106 testCommand="$(getBuildCommand $*)"
Jeff Gaston40660e72020-01-21 16:46:14 -0500107 cd "$workingDir"
Jeff Gastona6c665042020-07-22 12:57:33 -0400108 if eval $testCommand; then
Jeff Gastoncc0993d2019-04-02 18:02:44 -0400109 echo -e "$COLOR_WHITE"
110 echo
Jeff Gastona6c665042020-07-22 12:57:33 -0400111 echo '`'$testCommand'`' succeeded
Jeff Gaston599b9e32020-08-05 18:36:56 -0400112 return 0
Jeff Gastoncc0993d2019-04-02 18:02:44 -0400113 else
114 echo -e "$COLOR_WHITE"
115 echo
Jeff Gastona6c665042020-07-22 12:57:33 -0400116 echo '`'$testCommand'`' failed
Jeff Gaston599b9e32020-08-05 18:36:56 -0400117 return 1
Jeff Gastoncc0993d2019-04-02 18:02:44 -0400118 fi
119}
120
121function backupState() {
122 cd "$scriptPath"
123 backupDir="$1"
Jeff Gaston63234502019-07-09 13:47:31 -0400124 ./impl/backup-state.sh "$backupDir" "$workingDir"
Jeff Gastoncc0993d2019-04-02 18:02:44 -0400125}
126
127function restoreState() {
128 cd "$scriptPath"
129 backupDir="$1"
Jeff Gaston63234502019-07-09 13:47:31 -0400130 ./impl/restore-state.sh "$backupDir" "$workingDir"
Jeff Gastoncc0993d2019-04-02 18:02:44 -0400131}
132
133function clearState() {
134 restoreState /dev/null
135}
136
137echo
138echo "Making sure that we can reproduce the build failure"
139if runBuild ./gradlew $gradleArgs; then
140 echo
141 echo "This script failed to reproduce the build failure."
142 echo "If the build failure you were observing was in Android Studio, then:"
143 echo ' Were you launching Android Studio by running `./studiow`?'
144 echo " Try asking a team member why Android Studio is failing but gradlew is succeeding"
145 echo "If you previously observed a build failure, then this means one of:"
146 echo " The state of your build is different than when you started your previous build"
147 echo " You could ask a team member if they've seen this error."
148 echo " The build is nondeterministic"
149 echo " If this seems likely to you, then please open a bug."
150 exit 1
151else
152 echo
153 echo "Reproduced build failure"
154fi
155
156echo
157echo "Stopping the Gradle Daemon and rebuilding"
158cd "$supportRoot"
159./gradlew --stop || true
160if runBuild ./gradlew --no-daemon $gradleArgs; then
161 echo
162 echo "The build passed when disabling the Gradle Daemon"
163 echo "This suggests that there is some state saved in the Gradle Daemon that is causing a failure."
164 echo "Unfortunately, this script does not know how to diagnose this further."
165 echo "You could ask a team member if they've seen this error."
166 exit 1
167else
168 echo
169 echo "The build failed even with the Gradle Daemon disabled."
170 echo "This may mean that there is state stored in a file somewhere, triggering the build to fail."
171 echo "We will investigate the possibility of saved state next."
172 echo
173 backupState "$tempDir/prev"
174fi
175
176echo
177echo "Checking whether a clean build passes"
178clearState
179backupState "$tempDir/empty"
180successState="$tempDir/empty"
181if runBuild ./gradlew --no-daemon $gradleArgs; then
182 echo
183 echo "The clean build passed, so we can now investigate what cached state is triggering this build to fail."
184 backupState "$tempDir/clean"
185else
186 echo
Jeff Gastona6c665042020-07-22 12:57:33 -0400187 echo "The clean build also reproduced the issue."
188 echo "This may mean that everyone is observing this issue"
Jeff Gastoncc0993d2019-04-02 18:02:44 -0400189 echo "This may mean that something about your checkout is different from others'"
Jeff Gaston216c9702019-05-14 17:44:16 -0400190 echo "You may be interested in running development/simplify-build-failure/simplify-build-failure.sh to identify the minimal set of source files required to reproduce this error"
Jeff Gastoncc0993d2019-04-02 18:02:44 -0400191 echo "Checking the status of your checkout:"
192 checkStatus
193 exit 1
194fi
195
196echo
197echo "Checking whether a second build passes when starting from the output of the first clean build"
198if runBuild ./gradlew --no-daemon $gradleArgs; then
199 echo
200 echo "The next build after the clean build passed, so we can use the output of the first clean build as the successful state to compare against"
201 successState="$tempDir/clean"
202else
203 echo
204 echo "The next build after the clean build failed."
205 echo "Although this is unexpected, we should still be able to diagnose it."
206 echo "This might be slower than normal, though, because it may require us to rebuild more things more often"
207fi
208
209echo
210echo "Next we'll double-check that after restoring the failing state, the build fails"
211restoreState "$tempDir/prev"
212if runBuild ./gradlew --no-daemon $gradleArgs; then
213 echo
214 echo "After restoring the saved state, the build passed."
215 echo "This might mean that there is additional state being saved somewhere else that this script does not know about"
216 echo "This might mean that the success or failure status of the build is dependent on timestamps."
217 echo "This might mean that the build is nondeterministic."
218 echo "Unfortunately, this script does not know how to diagnose this further."
219 echo "You could:"
220 echo " Ask a team member if they know where the state may be stored"
221 echo " Ask a team member if they recognize the build error"
222 exit 1
223else
224 echo
225 echo "After restoring the saved state, the build failed. This confirms that this script is successfully saving and restoring the relevant state"
226fi
227
228# Now ask diff-filterer.py to run a binary search to determine what the relevant differences are between "$tempDir/prev" and "$tempDir/clean"
229echo
230echo "Binary-searching the contents of the two output directories until the relevant differences are identified."
231echo "This may take a while."
232echo
Jeff Gaston599b9e32020-08-05 18:36:56 -0400233filtererCommand="$(getBuildCommand \"$scriptPath/impl/restore-state.sh . $workingDir && cd $workingDir && ./gradlew --no-daemon $gradleArgs\")"
234if $supportRoot/development/file-utils/diff-filterer.py --assume-no-side-effects --assume-input-states-are-correct --work-path $tempDir $successState $tempDir/prev "$filtererCommand"; then
Jeff Gastoncc0993d2019-04-02 18:02:44 -0400235 echo
236 echo "There should be something wrong with the above file state"
237 echo "Hopefully the output from diff-filterer.py above is enough information for you to figure out what is wrong"
238 echo "If not, you could ask a team member about your original error message and see if they have any ideas"
239else
240 echo
241 echo "Something went wrong running diff-filterer.py"
242 echo "Maybe that means the build is nondeterministic"
243 echo "Maybe that means that there's something wrong with this script ($0)"
244fi