#!/bin/bash
set -eu
# Copyright 2020 Google Inc. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Tool to evaluate the transitive closure of the ninja dependency graph of the
# files and targets depending on a given target.
#
# i.e. the list of things that could change after changing a target.
readonly me=$(basename
"${0}")
readonly usage=
"usage: ${me} {options} target [target...]
Evaluate the reverse transitive closure of ninja targets depending on one or
more targets.
Options:
-(no)quiet Suppresses progress output to stderr and interactive
alias -(no)q prompts. By default, when stderr is a tty, progress gets
reported to stderr; when both stderr and stdin are tty,
the script asks user whether to
delete intermediate files.
When suppressed or not prompted, script always deletes the
temporary / intermediate files.
-sep=<delim> Use
'delim' as output field separator between notice
checksum and notice filename in notice output.
e.g. sep=
'\t'
(Default space)
-csv Shorthand
for -sep=
','
At minimum, before running this script, you must first run:
$ source build/envsetup.sh
$ lunch
$ m nothing
to setup the build environment, choose a target platform, and build the ninja
dependency graph.
"
function die() {
echo -e
"${*}" >&
2; exit
2; }
# Reads one input target per line from stdin; outputs (isnotice target) tuples.
#
# output target is a ninja target that the input target depends on
# isnotice in {0,1} with 1 for output targets believed to be license or notice
#
# only argument is the dependency depth indicator
function getDeps() {
(tr
'\n' '\0' | xargs -
0 "${ninja_bin}" -f
"${ninja_file}" -t query) \
| awk -v depth=
"${1}" '
BEGIN {
inoutput =
0
}
$
0 ~ /^\S\S*:$/ {
inoutput =
0
}
$
1 ==
"validations:" {
inoutput =
0
}
inoutput !=
0 {
print gensub(/^\s*/,
"",
"g")
" "depth
}
$
1 ==
"outputs:" {
inoutput =
1
}
'
}
if [ -z
"${ANDROID_BUILD_TOP}" ];
then
die
"${me}: Run 'lunch' to configure the build environment"
fi
if [ -z
"${TARGET_PRODUCT}" ];
then
die
"${me}: Run 'lunch' to configure the build environment"
fi
ninja_file=
"${ANDROID_BUILD_TOP}/out/combined-${TARGET_PRODUCT}.ninja"
if [ ! -f
"${ninja_file}" ];
then
die
"${me}: Run 'm nothing' to build the dependency graph"
fi
ninja_bin=
"${ANDROID_BUILD_TOP}/prebuilts/build-tools/linux-x86/bin/ninja"
if [ ! -x
"${ninja_bin}" ];
then
die
"${me}: Cannot find ninja executable expected at ${ninja_bin}"
fi
# parse the command-line
declare -a targets
# one or more targets to evaluate
quiet=false
# whether to suppress progress
sep=
" " # output separator between depth and target
use_stdin=false
# whether to read targets from stdin i.e. target -
while [ $
# -gt 0 ]; do
case
"${1:-}" in
-)
use_stdin=true
;;
-*)
flag=$(expr
"${1}" :
'^-*\(.*\)$')
case
"${flag:-}" in
q) ;&
quiet)
quiet=true;;
noq) ;&
noquiet)
quiet=false;;
csv)
sep=
",";;
sep)
sep=
"${2?"${usage}
"}"; shift;;
sep=*)
sep=$(expr
"${flag}" :
'^sep=\(.*\)$';;
*)
die
"Unknown flag ${1}"
;;
esac
;;
*)
targets+=(
"${1:-}")
;;
esac
shift
done
if [ ! -v targets[
0] ] && ! ${use_stdin};
then
die
"${usage}\n\nNo target specified."
fi
# showProgress when stderr is a tty
if [ -t
2 ] && ! ${quiet};
then
showProgress=true
else
showProgress=false
fi
# interactive when both stderr and stdin are tty
if ${showProgress} && [ -t
0 ];
then
interactive=true
else
interactive=false
fi
readonly tmpFiles=$(mktemp -d
"${TMPDIR}.tdeps.XXXXXXXXX")
if [ -z
"${tmpFiles}" ];
then
die
"${me}: unable to create temporary directory"
fi
# The deps files contain unique (isnotice target) tuples where
# isnotice in {0,1} with 1 when ninja target `target` is a license or notice.
readonly oldDeps=
"${tmpFiles}/old"
readonly newDeps=
"${tmpFiles}/new"
readonly allDeps=
"${tmpFiles}/all"
if ${use_stdin};
then # start deps by reading 1 target per line from stdin
awk
'
NF >
0 {
print gensub(/\s*$/,
"",
"g", gensub(/^\s*/,
"",
"g"))
" "0
}
' >"${newDeps}"
else # start with no deps by clearing file
: >
"${newDeps}"
fi
# extend deps by appending targets from command-line
for idx in
"${!targets[*]}";
do
echo "${targets[${idx}]} 0" >>
"${newDeps}"
done
# remove duplicates and start with new, old and all the same
sort -u <
"${newDeps}" >
"${allDeps}"
cp "${allDeps}" "${newDeps}"
cp "${allDeps}" "${oldDeps}"
# report depth of dependenciens when showProgress
depth=
0
while [ $(wc -l <
"${newDeps}") -gt
0 ];
do
if ${showProgress};
then
echo "depth ${depth} has "$(wc -l <
"${newDeps}")
" targets" >&
2
fi
depth=$(expr ${depth} +
1)
(
# recalculate dependencies by combining unique inputs of new deps w. old
cut -d\ -f1
"${newDeps}" | getDeps
"${depth}"
cat "${oldDeps}"
) | sort -n | awk
'
BEGIN {
prev =
""
}
{
depth = $NF
$NF =
""
gsub(/\s*$/,
"")
if ($
0 != prev) {
print gensub(/\s*$/,
"",
"g")
" "depth
}
prev = $
0
}
' >"${allDeps}"
# recalculate new dependencies as net additions to old dependencies
set +e
diff
"${oldDeps}" "${allDeps}" --old-line-format=
'' \
--new-line-format=
'%L' --unchanged-line-format=
'' >
"${newDeps}"
set -e
# recalculate old dependencies for next iteration
cp "${allDeps}" "${oldDeps}"
done
# found all deps -- clean up last iteration of old and new
rm -f
"${oldDeps}"
rm -f
"${newDeps}"
if ${showProgress};
then
echo $(wc -l <
"${allDeps}")
" targets" >&
2
fi
awk -v sep=
"${sep}" '{
depth = $NF
$NF =
""
gsub(/\s*$/,
"")
print depth sep $
0
}
' "${allDeps}" | sort -n
if ${interactive};
then
echo -n
"$(date '+%F %-k:%M:%S') Delete ${tmpFiles} ? [n] " >&
2
read answer
case
"${answer}" in [yY]*)
rm -fr
"${tmpFiles}";; esac
else
rm -fr
"${tmpFiles}"
fi