code-format.sh 6.3 KB

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