247 lines
6.6 KiB
Bash
247 lines
6.6 KiB
Bash
|
#!/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
|
||
|
}
|
||
|
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
|