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