blob: 02b7dcb736b94fd28914e9b2a9faf51a61258a66 [file] [log] [blame]
Bob Badour02ad5e42020-03-04 14:43:22 -08001#!/bin/bash
2
3set -eu
4
5# Copyright 2020 Google Inc. All rights reserved.
6#
7# Licensed under the Apache License, Version 2.0 (the "License");
8# you may not use this file except in compliance with the License.
9# You may obtain a copy of the License at
10#
11# http://www.apache.org/licenses/LICENSE-2.0
12#
13# Unless required by applicable law or agreed to in writing, software
14# distributed under the License is distributed on an "AS IS" BASIS,
15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16# See the License for the specific language governing permissions and
17# limitations under the License.
18
19# Tool to evaluate the transitive closure of the ninja dependency graph of the
20# files and targets depending on a given target.
21#
22# i.e. the list of things that could change after changing a target.
23
24readonly me=$(basename "${0}")
25
26readonly usage="usage: ${me} {options} target [target...]
27
28Evaluate the reverse transitive closure of ninja targets depending on one or
29more targets.
30
31Options:
32
33 -(no)quiet Suppresses progress output to stderr and interactive
34 alias -(no)q prompts. By default, when stderr is a tty, progress gets
35 reported to stderr; when both stderr and stdin are tty,
36 the script asks user whether to delete intermediate files.
37 When suppressed or not prompted, script always deletes the
38 temporary / intermediate files.
39 -sep=<delim> Use 'delim' as output field separator between notice
40 checksum and notice filename in notice output.
41 e.g. sep='\t'
42 (Default space)
43 -csv Shorthand for -sep=','
44
45At minimum, before running this script, you must first run:
46$ source build/envsetup.sh
47$ lunch
48$ m nothing
49to setup the build environment, choose a target platform, and build the ninja
50dependency graph.
51"
52
53function die() { echo -e "${*}" >&2; exit 2; }
54
55# Reads one input target per line from stdin; outputs (isnotice target) tuples.
56#
57# output target is a ninja target that the input target depends on
58# isnotice in {0,1} with 1 for output targets believed to be license or notice
59#
60# only argument is the dependency depth indicator
61function getDeps() {
62 (tr '\n' '\0' | xargs -0 "${ninja_bin}" -f "${ninja_file}" -t query) \
63 | awk -v depth="${1}" '
64 BEGIN {
65 inoutput = 0
66 }
67 $0 ~ /^\S\S*:$/ {
68 inoutput = 0
69 }
70 inoutput != 0 {
71 print gensub(/^\s*/, "", "g")" "depth
72 }
73 $1 == "outputs:" {
74 inoutput = 1
75 }
76 '
77}
78
79
80if [ -z "${ANDROID_BUILD_TOP}" ]; then
81 die "${me}: Run 'lunch' to configure the build environment"
82fi
83
84if [ -z "${TARGET_PRODUCT}" ]; then
85 die "${me}: Run 'lunch' to configure the build environment"
86fi
87
88ninja_file="${ANDROID_BUILD_TOP}/out/combined-${TARGET_PRODUCT}.ninja"
89if [ ! -f "${ninja_file}" ]; then
90 die "${me}: Run 'm nothing' to build the dependency graph"
91fi
92
93ninja_bin="${ANDROID_BUILD_TOP}/prebuilts/build-tools/linux-x86/bin/ninja"
94if [ ! -x "${ninja_bin}" ]; then
95 die "${me}: Cannot find ninja executable expected at ${ninja_bin}"
96fi
97
98
99# parse the command-line
100
101declare -a targets # one or more targets to evaluate
102
103quiet=false # whether to suppress progress
104
105sep=" " # output separator between depth and target
106
107use_stdin=false # whether to read targets from stdin i.e. target -
108
109while [ $# -gt 0 ]; do
110 case "${1:-}" in
111 -)
112 use_stdin=true
113 ;;
114 -*)
115 flag=$(expr "${1}" : '^-*\(.*\)$')
116 case "${flag:-}" in
117 q) ;&
118 quiet)
119 quiet=true;;
120 noq) ;&
121 noquiet)
122 quiet=false;;
123 csv)
124 sep=",";;
125 sep)
126 sep="${2?"${usage}"}"; shift;;
127 sep=*)
128 sep=$(expr "${flag}" : '^sep=\(.*\)$';;
129 *)
130 die "Unknown flag ${1}"
131 ;;
132 esac
133 ;;
134 *)
135 targets+=("${1:-}")
136 ;;
137 esac
138 shift
139done
140
141if [ ! -v targets[0] ] && ! ${use_stdin}; then
142 die "${usage}\n\nNo target specified."
143fi
144
145# showProgress when stderr is a tty
146if [ -t 2 ] && ! ${quiet}; then
147 showProgress=true
148else
149 showProgress=false
150fi
151
152# interactive when both stderr and stdin are tty
153if ${showProgress} && [ -t 0 ]; then
154 interactive=true
155else
156 interactive=false
157fi
158
159
160readonly tmpFiles=$(mktemp -d "${TMPDIR}.tdeps.XXXXXXXXX")
161if [ -z "${tmpFiles}" ]; then
162 die "${me}: unable to create temporary directory"
163fi
164
165# The deps files contain unique (isnotice target) tuples where
166# isnotice in {0,1} with 1 when ninja target `target` is a license or notice.
167readonly oldDeps="${tmpFiles}/old"
168readonly newDeps="${tmpFiles}/new"
169readonly allDeps="${tmpFiles}/all"
170
171if ${use_stdin}; then # start deps by reading 1 target per line from stdin
172 awk '
173 NF > 0 {
174 print gensub(/\s*$/, "", "g", gensub(/^\s*/, "", "g"))" "0
175 }
176 ' >"${newDeps}"
177else # start with no deps by clearing file
178 : >"${newDeps}"
179fi
180
181# extend deps by appending targets from command-line
182for idx in "${!targets[*]}"; do
183 echo "${targets[${idx}]} 0" >>"${newDeps}"
184done
185
186# remove duplicates and start with new, old and all the same
187sort -u <"${newDeps}" >"${allDeps}"
188cp "${allDeps}" "${newDeps}"
189cp "${allDeps}" "${oldDeps}"
190
191# report depth of dependenciens when showProgress
192depth=0
193
194while [ $(wc -l < "${newDeps}") -gt 0 ]; do
195 if ${showProgress}; then
196 echo "depth ${depth} has "$(wc -l < "${newDeps}")" targets" >&2
197 fi
198 depth=$(expr ${depth} + 1)
199 ( # recalculate dependencies by combining unique inputs of new deps w. old
200 cut -d\ -f1 "${newDeps}" | getDeps "${depth}"
201 cat "${oldDeps}"
202 ) | sort -n | awk '
203 BEGIN {
204 prev = ""
205 }
206 {
207 depth = $NF
208 $NF = ""
209 gsub(/\s*$/, "")
210 if ($0 != prev) {
211 print gensub(/\s*$/, "", "g")" "depth
212 }
213 prev = $0
214 }
215 ' >"${allDeps}"
216 # recalculate new dependencies as net additions to old dependencies
217 set +e
218 diff "${oldDeps}" "${allDeps}" --old-line-format='' \
219 --new-line-format='%L' --unchanged-line-format='' > "${newDeps}"
220 set -e
221 # recalculate old dependencies for next iteration
222 cp "${allDeps}" "${oldDeps}"
223done
224
225# found all deps -- clean up last iteration of old and new
226rm -f "${oldDeps}"
227rm -f "${newDeps}"
228
229if ${showProgress}; then
230 echo $(wc -l < "${allDeps}")" targets" >&2
231fi
232
233awk -v sep="${sep}" '{
234 depth = $NF
235 $NF = ""
236 gsub(/\s*$/, "")
237 print depth sep $0
238}' "${allDeps}" | sort -n
239
240if ${interactive}; then
241 echo -n "$(date '+%F %-k:%M:%S') Delete ${tmpFiles} ? [n] " >&2
242 read answer
243 case "${answer}" in [yY]*) rm -fr "${tmpFiles}";; esac
244else
245 rm -fr "${tmpFiles}"
246fi