1#!/bin/bash 2 3set -eu 4 5# Copyright 2020 Google Inc. All rights reserved. 6# 7# Licensed under the Apache License, Version 2.0 (the "License"); 8# you may not use this file except in compliance with the License. 9# You may obtain a copy of the License at 10# 11# http://www.apache.org/licenses/LICENSE-2.0 12# 13# Unless required by applicable law or agreed to in writing, software 14# distributed under the License is distributed on an "AS IS" BASIS, 15# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16# See the License for the specific language governing permissions and 17# limitations under the License. 18 19# Tool to evaluate the transitive closure of the ninja dependency graph of the 20# files and targets depending on a given target. 21# 22# i.e. the list of things that could change after changing a target. 23 24readonly me=$(basename "${0}") 25 26readonly usage="usage: ${me} {options} target [target...] 27 28Evaluate the reverse transitive closure of ninja targets depending on one or 29more targets. 30 31Options: 32 33 -(no)quiet Suppresses progress output to stderr and interactive 34 alias -(no)q prompts. By default, when stderr is a tty, progress gets 35 reported to stderr; when both stderr and stdin are tty, 36 the script asks user whether to delete intermediate files. 37 When suppressed or not prompted, script always deletes the 38 temporary / intermediate files. 39 -sep=<delim> Use 'delim' as output field separator between notice 40 checksum and notice filename in notice output. 41 e.g. sep='\t' 42 (Default space) 43 -csv Shorthand for -sep=',' 44 45At minimum, before running this script, you must first run: 46$ source build/envsetup.sh 47$ lunch 48$ m nothing 49to setup the build environment, choose a target platform, and build the ninja 50dependency graph. 51" 52 53function die() { echo -e "${*}" >&2; exit 2; } 54 55# Reads one input target per line from stdin; outputs (isnotice target) tuples. 56# 57# output target is a ninja target that the input target depends on 58# isnotice in {0,1} with 1 for output targets believed to be license or notice 59# 60# only argument is the dependency depth indicator 61function getDeps() { 62 (tr '\n' '\0' | xargs -0 "${ninja_bin}" -f "${ninja_file}" -t query) \ 63 | awk -v depth="${1}" ' 64 BEGIN { 65 inoutput = 0 66 } 67 $0 ~ /^\S\S*:$/ { 68 inoutput = 0 69 } 70 $1 == "validations:" { 71 inoutput = 0 72 } 73 inoutput != 0 { 74 print gensub(/^\s*/, "", "g")" "depth 75 } 76 $1 == "outputs:" { 77 inoutput = 1 78 } 79 ' 80} 81 82 83if [ -z "${ANDROID_BUILD_TOP}" ]; then 84 die "${me}: Run 'lunch' to configure the build environment" 85fi 86 87if [ -z "${TARGET_PRODUCT}" ]; then 88 die "${me}: Run 'lunch' to configure the build environment" 89fi 90 91ninja_file="${ANDROID_BUILD_TOP}/out/combined-${TARGET_PRODUCT}.ninja" 92if [ ! -f "${ninja_file}" ]; then 93 die "${me}: Run 'm nothing' to build the dependency graph" 94fi 95 96ninja_bin="${ANDROID_BUILD_TOP}/prebuilts/build-tools/linux-x86/bin/ninja" 97if [ ! -x "${ninja_bin}" ]; then 98 die "${me}: Cannot find ninja executable expected at ${ninja_bin}" 99fi 100 101 102# parse the command-line 103 104declare -a targets # one or more targets to evaluate 105 106quiet=false # whether to suppress progress 107 108sep=" " # output separator between depth and target 109 110use_stdin=false # whether to read targets from stdin i.e. target - 111 112while [ $# -gt 0 ]; do 113 case "${1:-}" in 114 -) 115 use_stdin=true 116 ;; 117 -*) 118 flag=$(expr "${1}" : '^-*\(.*\)$') 119 case "${flag:-}" in 120 q) ;& 121 quiet) 122 quiet=true;; 123 noq) ;& 124 noquiet) 125 quiet=false;; 126 csv) 127 sep=",";; 128 sep) 129 sep="${2?"${usage}"}"; shift;; 130 sep=*) 131 sep=$(expr "${flag}" : '^sep=\(.*\)$';; 132 *) 133 die "Unknown flag ${1}" 134 ;; 135 esac 136 ;; 137 *) 138 targets+=("${1:-}") 139 ;; 140 esac 141 shift 142done 143 144if [ ! -v targets[0] ] && ! ${use_stdin}; then 145 die "${usage}\n\nNo target specified." 146fi 147 148# showProgress when stderr is a tty 149if [ -t 2 ] && ! ${quiet}; then 150 showProgress=true 151else 152 showProgress=false 153fi 154 155# interactive when both stderr and stdin are tty 156if ${showProgress} && [ -t 0 ]; then 157 interactive=true 158else 159 interactive=false 160fi 161 162 163readonly tmpFiles=$(mktemp -d "${TMPDIR}.tdeps.XXXXXXXXX") 164if [ -z "${tmpFiles}" ]; then 165 die "${me}: unable to create temporary directory" 166fi 167 168# The deps files contain unique (isnotice target) tuples where 169# isnotice in {0,1} with 1 when ninja target `target` is a license or notice. 170readonly oldDeps="${tmpFiles}/old" 171readonly newDeps="${tmpFiles}/new" 172readonly allDeps="${tmpFiles}/all" 173 174if ${use_stdin}; then # start deps by reading 1 target per line from stdin 175 awk ' 176 NF > 0 { 177 print gensub(/\s*$/, "", "g", gensub(/^\s*/, "", "g"))" "0 178 } 179 ' >"${newDeps}" 180else # start with no deps by clearing file 181 : >"${newDeps}" 182fi 183 184# extend deps by appending targets from command-line 185for idx in "${!targets[*]}"; do 186 echo "${targets[${idx}]} 0" >>"${newDeps}" 187done 188 189# remove duplicates and start with new, old and all the same 190sort -u <"${newDeps}" >"${allDeps}" 191cp "${allDeps}" "${newDeps}" 192cp "${allDeps}" "${oldDeps}" 193 194# report depth of dependenciens when showProgress 195depth=0 196 197while [ $(wc -l < "${newDeps}") -gt 0 ]; do 198 if ${showProgress}; then 199 echo "depth ${depth} has "$(wc -l < "${newDeps}")" targets" >&2 200 fi 201 depth=$(expr ${depth} + 1) 202 ( # recalculate dependencies by combining unique inputs of new deps w. old 203 cut -d\ -f1 "${newDeps}" | getDeps "${depth}" 204 cat "${oldDeps}" 205 ) | sort -n | awk ' 206 BEGIN { 207 prev = "" 208 } 209 { 210 depth = $NF 211 $NF = "" 212 gsub(/\s*$/, "") 213 if ($0 != prev) { 214 print gensub(/\s*$/, "", "g")" "depth 215 } 216 prev = $0 217 } 218 ' >"${allDeps}" 219 # recalculate new dependencies as net additions to old dependencies 220 set +e 221 diff "${oldDeps}" "${allDeps}" --old-line-format='' \ 222 --new-line-format='%L' --unchanged-line-format='' > "${newDeps}" 223 set -e 224 # recalculate old dependencies for next iteration 225 cp "${allDeps}" "${oldDeps}" 226done 227 228# found all deps -- clean up last iteration of old and new 229rm -f "${oldDeps}" 230rm -f "${newDeps}" 231 232if ${showProgress}; then 233 echo $(wc -l < "${allDeps}")" targets" >&2 234fi 235 236awk -v sep="${sep}" '{ 237 depth = $NF 238 $NF = "" 239 gsub(/\s*$/, "") 240 print depth sep $0 241}' "${allDeps}" | sort -n 242 243if ${interactive}; then 244 echo -n "$(date '+%F %-k:%M:%S') Delete ${tmpFiles} ? [n] " >&2 245 read answer 246 case "${answer}" in [yY]*) rm -fr "${tmpFiles}";; esac 247else 248 rm -fr "${tmpFiles}" 249fi 250