blob: 410f5c0b79da5ee2733e788559c2b39282134ad9 [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 }
Bob Badour0174ae32021-11-23 12:12:06 -080070 $1 == "validations:" {
71 inoutput = 0
72 }
Bob Badour02ad5e42020-03-04 14:43:22 -080073 inoutput != 0 {
74 print gensub(/^\s*/, "", "g")" "depth
75 }
76 $1 == "outputs:" {
77 inoutput = 1
78 }
79 '
80}
81
82
83if [ -z "${ANDROID_BUILD_TOP}" ]; then
84 die "${me}: Run 'lunch' to configure the build environment"
85fi
86
87if [ -z "${TARGET_PRODUCT}" ]; then
88 die "${me}: Run 'lunch' to configure the build environment"
89fi
90
91ninja_file="${ANDROID_BUILD_TOP}/out/combined-${TARGET_PRODUCT}.ninja"
92if [ ! -f "${ninja_file}" ]; then
93 die "${me}: Run 'm nothing' to build the dependency graph"
94fi
95
96ninja_bin="${ANDROID_BUILD_TOP}/prebuilts/build-tools/linux-x86/bin/ninja"
97if [ ! -x "${ninja_bin}" ]; then
98 die "${me}: Cannot find ninja executable expected at ${ninja_bin}"
99fi
100
101
102# parse the command-line
103
104declare -a targets # one or more targets to evaluate
105
106quiet=false # whether to suppress progress
107
108sep=" " # output separator between depth and target
109
110use_stdin=false # whether to read targets from stdin i.e. target -
111
112while [ $# -gt 0 ]; do
113 case "${1:-}" in
114 -)
115 use_stdin=true
116 ;;
117 -*)
118 flag=$(expr "${1}" : '^-*\(.*\)$')
119 case "${flag:-}" in
120 q) ;&
121 quiet)
122 quiet=true;;
123 noq) ;&
124 noquiet)
125 quiet=false;;
126 csv)
127 sep=",";;
128 sep)
129 sep="${2?"${usage}"}"; shift;;
130 sep=*)
131 sep=$(expr "${flag}" : '^sep=\(.*\)$';;
132 *)
133 die "Unknown flag ${1}"
134 ;;
135 esac
136 ;;
137 *)
138 targets+=("${1:-}")
139 ;;
140 esac
141 shift
142done
143
144if [ ! -v targets[0] ] && ! ${use_stdin}; then
145 die "${usage}\n\nNo target specified."
146fi
147
148# showProgress when stderr is a tty
149if [ -t 2 ] && ! ${quiet}; then
150 showProgress=true
151else
152 showProgress=false
153fi
154
155# interactive when both stderr and stdin are tty
156if ${showProgress} && [ -t 0 ]; then
157 interactive=true
158else
159 interactive=false
160fi
161
162
163readonly tmpFiles=$(mktemp -d "${TMPDIR}.tdeps.XXXXXXXXX")
164if [ -z "${tmpFiles}" ]; then
165 die "${me}: unable to create temporary directory"
166fi
167
168# The deps files contain unique (isnotice target) tuples where
169# isnotice in {0,1} with 1 when ninja target `target` is a license or notice.
170readonly oldDeps="${tmpFiles}/old"
171readonly newDeps="${tmpFiles}/new"
172readonly allDeps="${tmpFiles}/all"
173
174if ${use_stdin}; then # start deps by reading 1 target per line from stdin
175 awk '
176 NF > 0 {
177 print gensub(/\s*$/, "", "g", gensub(/^\s*/, "", "g"))" "0
178 }
179 ' >"${newDeps}"
180else # start with no deps by clearing file
181 : >"${newDeps}"
182fi
183
184# extend deps by appending targets from command-line
185for idx in "${!targets[*]}"; do
186 echo "${targets[${idx}]} 0" >>"${newDeps}"
187done
188
189# remove duplicates and start with new, old and all the same
190sort -u <"${newDeps}" >"${allDeps}"
191cp "${allDeps}" "${newDeps}"
192cp "${allDeps}" "${oldDeps}"
193
194# report depth of dependenciens when showProgress
195depth=0
196
197while [ $(wc -l < "${newDeps}") -gt 0 ]; do
198 if ${showProgress}; then
199 echo "depth ${depth} has "$(wc -l < "${newDeps}")" targets" >&2
200 fi
201 depth=$(expr ${depth} + 1)
202 ( # recalculate dependencies by combining unique inputs of new deps w. old
203 cut -d\ -f1 "${newDeps}" | getDeps "${depth}"
204 cat "${oldDeps}"
205 ) | sort -n | awk '
206 BEGIN {
207 prev = ""
208 }
209 {
210 depth = $NF
211 $NF = ""
212 gsub(/\s*$/, "")
213 if ($0 != prev) {
214 print gensub(/\s*$/, "", "g")" "depth
215 }
216 prev = $0
217 }
218 ' >"${allDeps}"
219 # recalculate new dependencies as net additions to old dependencies
220 set +e
221 diff "${oldDeps}" "${allDeps}" --old-line-format='' \
222 --new-line-format='%L' --unchanged-line-format='' > "${newDeps}"
223 set -e
224 # recalculate old dependencies for next iteration
225 cp "${allDeps}" "${oldDeps}"
226done
227
228# found all deps -- clean up last iteration of old and new
229rm -f "${oldDeps}"
230rm -f "${newDeps}"
231
232if ${showProgress}; then
233 echo $(wc -l < "${allDeps}")" targets" >&2
234fi
235
236awk -v sep="${sep}" '{
237 depth = $NF
238 $NF = ""
239 gsub(/\s*$/, "")
240 print depth sep $0
241}' "${allDeps}" | sort -n
242
243if ${interactive}; then
244 echo -n "$(date '+%F %-k:%M:%S') Delete ${tmpFiles} ? [n] " >&2
245 read answer
246 case "${answer}" in [yY]*) rm -fr "${tmpFiles}";; esac
247else
248 rm -fr "${tmpFiles}"
249fi