|  | #!/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 |