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