code-format.sh (6777B)
1 #!/usr/bin/env bash 2 # Copyright 2020, The Tor Project, Inc. 3 # See LICENSE for licensing information. 4 5 # 6 # DO NOT COMMIT OR MERGE CODE THAT IS RUN THROUGH THIS TOOL YET. 7 # 8 # WE ARE STILL DISCUSSING OUR DESIRED STYLE AND ITERATING ON IT. 9 # (12 Feb 2020) 10 # 11 12 # This script runs "clang-format" and "codetool" in sequence over each of its 13 # arguments. It either replaces the original, or says whether anything has 14 # changed, depending on its arguments. 15 # 16 # We can't just use clang-format directly, since we also want to use codetool 17 # to reformat a few things back to how we want them, and we want avoid changing 18 # the mtime on files that didn't actually change. 19 # 20 # Use "-i" to edit the file in-place. 21 # Use "-c" to exit with a nonzero exit status if any file needs to change. 22 # Use "-d" to emit diffs. 23 # 24 # The "-a" option tells us to run over every Tor source file. 25 # The "-v" option tells us to be verbose. 26 27 set -e 28 29 ALL=0 30 GITDIFF=0 31 GITIDX=0 32 DIFFMODE=0 33 CHECKMODE=0 34 CHANGEMODE=0 35 36 SCRIPT_NAME=$(basename "$0") 37 SCRIPT_DIR=$(dirname "$0") 38 SRC_DIR="${SCRIPT_DIR}/../../src" 39 40 function usage() { 41 echo "$SCRIPT_NAME [-h] [-c|-d|-i] [-v] [-a|-G|files...]" 42 echo 43 echo " flags:" 44 echo " -h: show this help text" 45 echo " -c: check whether files are correctly formatted" 46 echo " -d: print a diff for the changes that would be applied" 47 echo " -i: change files in-place" 48 echo " -a: run over all the C files in Tor" 49 echo " -v: verbose mode" 50 echo " -g: look at the files that have changed in git." 51 echo " -G: look at the files that are staged for the git commit." 52 echo 53 echo "EXAMPLES" 54 echo 55 echo " $SCRIPT_NAME -a -i" 56 echo " rewrite every file in place, whether it has changed or not." 57 echo " $SCRIPT_NAME -a -d" 58 echo " as above, but only display the changes." 59 echo " $SCRIPT_NAME -g -i" 60 echo " update every file that you have changed in the git working tree." 61 echo " $SCRIPT_NAME -G -c" 62 echo " exit with an error if any staged changes are not well-formatted." 63 } 64 65 FILEARGS_OK=1 66 67 while getopts "acdgGhiv" opt; do 68 case "$opt" in 69 h) usage 70 exit 0 71 ;; 72 a) ALL=1 73 FILEARGS_OK=0 74 ;; 75 g) GITDIFF=1 76 FILEARGS_OK=0 77 ;; 78 G) GITIDX=1 79 FILEARGS_OK=0 80 ;; 81 c) CHECKMODE=1 82 ;; 83 d) DIFFMODE=1 84 ;; 85 i) CHANGEMODE=1 86 ;; 87 v) VERBOSE=1 88 ;; 89 *) echo 90 usage 91 exit 1 92 ;; 93 esac 94 done 95 # get rid of the flags; keep the filenames. 96 shift $((OPTIND - 1)) 97 98 # Define a verbose function. 99 if [[ $VERBOSE = 1 ]]; then 100 function note() 101 { 102 echo "$@" 103 } 104 else 105 function note() 106 { 107 true 108 } 109 fi 110 111 # We have to be in at least one mode, or we can't do anything 112 if [[ $CHECKMODE = 0 && $DIFFMODE = 0 && $CHANGEMODE = 0 ]]; then 113 echo "Nothing to do. You need to specify -c, -d, or -i." 114 echo "Try $SCRIPT_NAME -h for more information." 115 exit 0 116 fi 117 118 # We don't want to "give an error if anything would change" if we're 119 # actually trying to change things. 120 if [[ $CHECKMODE = 1 && $CHANGEMODE = 1 ]]; then 121 echo "It doesn't make sense to use -c and -i together." 122 exit 0 123 fi 124 # It doesn't make sense to look at "all files" and "git files" 125 if [[ $((ALL + GITIDX + GITDIFF)) -gt 1 ]]; then 126 echo "It doesn't make sense to use more than one of -a, -g, or -G together." 127 exit 0 128 fi 129 130 if [[ $FILEARGS_OK = 1 ]]; then 131 # The filenames are on the command-line. 132 INPUTS=("${@}") 133 else 134 if [[ "$#" != 0 ]]; then 135 echo "Can't use -a, -g, or -G with additional command-line arguments." 136 exit 1 137 fi 138 fi 139 140 if [[ $ALL = 1 ]]; then 141 # We're in "all" mode -- use find(1) to find the filenames. 142 mapfile -d '' INPUTS < <(find "${SRC_DIR}"/{lib,core,feature,app,test,tools} -name '[^.]*.[ch]' -print0) 143 elif [[ $GITIDX = 1 ]]; then 144 # We're in "git index" mode -- use git diff --cached to find the filenames 145 # that are changing in the index, then strip out the ones that 146 # aren't C. 147 mapfile INPUTS < <(git diff --name-only --cached --diff-filter=AMCR | grep '\.[ch]$') 148 elif [[ $GITDIFF = 1 ]]; then 149 # We are in 'git diff' mode -- we want everything that changed, including 150 # the index and the working tree. 151 # 152 # TODO: There might be a better way to do this. 153 mapfile INPUTS < <(git diff --name-only --cached --diff-filter=AMCR | grep '\.[ch]$'; git diff --name-only --diff-filter=AMCR | grep '\.[ch]$' ) 154 fi 155 156 if [[ $GITIDX = 1 ]]; then 157 # If we're running in git mode, we need to stash all the changes that 158 # we don't want to look at. This is necessary even though we're only 159 # looking at the changed files, since we might have the file only 160 # partially staged. 161 note "Stashing unstaged changes" 162 git stash -q --keep-index 163 # For some reasons, shellcheck is not seeing that we can call this 164 # function from the trap below. 165 # shellcheck disable=SC2317,SC2329 166 function restoregit() { 167 note "Restoring git state" 168 git stash pop -q 169 } 170 else 171 # For some reasons, shellcheck is not seeing that we can call this 172 # function from the trap below. 173 # shellcheck disable=SC2317,SC2329 174 function restoregit() { 175 true 176 } 177 fi 178 179 ANY_CHANGED=0 180 181 tmpfname="" 182 183 # 184 # Set up a trap handler to make sure that on exit, we remove our 185 # tmpfile and un-stash the git environment (if appropriate) 186 # 187 trap 'if [ -n "${tmpfname}" ]; then rm -f "${tmpfname}"; fi; restoregit' 0 188 189 for fname in "${INPUTS[@]}"; do 190 note "Inspecting $fname..." 191 tmpfname="${fname}.$$.clang_fmt.tmp" 192 rm -f "${tmpfname}" 193 clang-format --style=file "${fname}" > "${tmpfname}" 194 "${SCRIPT_DIR}/codetool.py" "${tmpfname}" 195 196 changed=not_set 197 198 if [[ $DIFFMODE = 1 ]]; then 199 # If we're running diff for its output, we can also use it 200 # to compare the files. 201 if diff -u "${fname}" "${tmpfname}"; then 202 changed=0 203 else 204 changed=1 205 fi 206 else 207 # We aren't running diff, so we have to compare the files with cmp. 208 if cmp "${fname}" "${tmpfname}" >/dev/null 2>&1; then 209 changed=0 210 else 211 changed=1 212 fi 213 fi 214 215 if [[ $changed = 1 ]]; then 216 note "Found a change in $fname" 217 ANY_CHANGED=1 218 219 if [[ $CHANGEMODE = 1 ]]; then 220 mv "${tmpfname}" "${fname}" 221 fi 222 fi 223 224 rm -f "${tmpfname}" 225 done 226 227 exitcode=0 228 229 if [[ $CHECKMODE = 1 ]]; then 230 if [[ $ANY_CHANGED = 1 ]]; then 231 note "Found at least one misformatted file; check failed" 232 exitcode=1 233 else 234 note "No changes found." 235 fi 236 fi 237 238 exit $exitcode