Imported Upstream version 1.12.0
[platform/core/ml/nnfw.git] / infra / command / format
1 #!/bin/bash
2
3 INVALID_EXIT=0
4 FILES_TO_CHECK=()
5 DIRECTORIES_TO_BE_TESTED=()
6 DIRECTORIES_NOT_TO_BE_TESTED=()
7 CLANG_FORMAT_CANDIDATES=()
8 PATCH_FILE=format.patch
9 CHECK_DIFF_ONLY="0"
10 CHECK_STAGED_ONLY="0"
11
12 function Usage()
13 {
14   echo "Usage: $0 $(basename ${BASH_SOURCE[0]}) [OPTIONS] [<file|dir> ...]"
15   echo "If no arguments are specified, it formats all nnas codes"
16   echo "If <file>s are given, it reformats the files"
17   echo ""
18   echo "Options:"
19   echo "      --clang-format <TOOL>     clang format bin (default: clang-format-3.9, clang-format)"
20   echo "      --diff-only               check diff files with master"
21   echo "      --staged-only             check git staged files"
22 }
23
24 while [[ $# -gt 0 ]]
25 do
26   arg="$1"
27   case $arg in
28     -h|--help|help)
29       Usage
30       exit 0
31       ;;
32     --clang-format)
33       CLANG_FORMAT_CANDIDATES=($2)
34       shift 2
35       ;;
36     --clang-format=*)
37       CLANG_FORMAT_CANDIDATES=(${1#*=})
38       shift
39       ;;
40     --staged-only)
41       CHECK_STAGED_ONLY="1"
42       CHECK_DIFF_ONLY="1"
43       shift
44       ;;
45     --diff-only)
46       CHECK_DIFF_ONLY="1"
47       shift
48       ;;
49     *)
50       DIRECTORIES_TO_BE_TESTED+=($1)
51       shift
52       ;;
53   esac
54 done
55
56 function pushd () {
57   command pushd "$@" > /dev/null
58 }
59
60 function popd () {
61   command popd "$@" > /dev/null
62 }
63
64 function command_exists() {
65   command -v $1 > /dev/null 2>&1
66 }
67
68 function exclude_symbolic_links() {
69   # Check all files (CMakeLists.txt, *.cl, ... not only for C++, Python)
70   if [[ ${#FILES_TO_CHECK} -ne 0 ]]; then
71     FILES_EXCLUDE_SYMLINKS=$(file ${FILES_TO_CHECK} | grep -v "symbolic link" | cut -d':' -f1)
72     FILES_TO_CHECK=${FILES_EXCLUDE_SYMLINKS}
73   fi
74 }
75
76 function check_newline() {
77   FILES_TO_CHECK_CR=()
78   for f in ${FILES_TO_CHECK[@]}; do
79     # Manually ignore style checking
80     if [[ ${f} == !(*.svg|*.pdf|*.png) ]]; then
81       FILES_TO_CHECK_CR+=("${f}")
82     fi
83   done
84
85   # Check all files (CMakeLists.txt, *.cl, ... not only for C++, Python)
86   if [[ ${#FILES_TO_CHECK_CR} -ne 0 ]]; then
87     CRCHECK=$(file ${FILES_TO_CHECK_CR} | grep 'with CR')
88   else
89     return
90   fi
91   FILES_TO_FIX=($(echo "$CRCHECK" | grep "with CRLF line" | cut -d':' -f1))
92   for f in ${FILES_TO_FIX[@]}; do
93     tr -d '\r' < $f > $f.fixed && cat $f.fixed > $f && rm $f.fixed
94   done
95   FILES_TO_FIX=($(echo "${CRCHECK}" | grep "with CR line" | cut -d':' -f1))
96   for f in ${FILES_TO_FIX[@]}; do
97     tr '\r' '\n' < $f > $f.fixed && cat $f.fixed > $f && rm $f.fixed
98   done
99   # Check no new line at end of file
100   for f in ${FILES_TO_CHECK_CR[@]}; do
101     if diff /dev/null "$f" | tail -1 | grep '^\\ No newline' > /dev/null; then
102       echo >> "$f"
103     fi
104   done
105 }
106
107 function check_permission() {
108   # Check all files except script
109   FILES_TO_CHECK_PERMISSION=()
110   for f in ${FILES_TO_CHECK[@]}; do
111     # Manually ignore permission checking
112     if [[ ${f} == !(nnas|nnfw|nncc|*.sh|*.py|*/gradlew) ]] || [[ ${f} == tests/nnapi/specs/**/*.py ]]; then
113       FILES_TO_CHECK_PERMISSION+=("${f}")
114     fi
115   done
116
117   if [[ ${#FILES_TO_CHECK_PERMISSION} -eq 0 ]]; then
118     return
119   fi
120   for FILE_TO_CHECK in ${FILES_TO_CHECK_PERMISSION[@]}; do
121     RESULT=$(stat -c '%A' ${FILE_TO_CHECK} | grep 'x')
122     if [ "${RESULT}" != "" ]; then
123       chmod a-x ${FILE_TO_CHECK}
124     fi
125   done
126 }
127
128 function check_cpp_files() {
129   if [[ ${__Check_CPP} -eq 0 ]]; then
130     echo "[SKIPPED] C/C++ check is skipped"
131     return
132   fi
133
134   CLANG_FORMAT_CANDIDATES+=("clang-format-3.9")
135   for CLANG_FORMAT_CANDIDATE in ${CLANG_FORMAT_CANDIDATES[@]}; do
136     if command_exists ${CLANG_FORMAT_CANDIDATE} ; then
137       CLANG_FORMAT="${CLANG_FORMAT_CANDIDATE}"
138       break
139     fi
140   done
141
142   if [[ -z ${CLANG_FORMAT}  ]]; then
143     echo "[ERROR] clang-format-3.9 is unavailable"
144     echo
145     echo "        Please install clang-format-3.9 before running format check"
146     exit 1
147   fi
148
149   # Migration to clang-format-8
150   # TODO Remove this after migration to clang-format-8
151   CLANG_FORMAT_8="clang-format-8"
152   if ! command_exists $CLANG_FORMAT_8_CANDIDATE; then
153     echo "[ERROR] clang-format-8 is unavailable"
154     echo
155     echo "        Please install clang-format-8 before running format check"
156     echo "        (or use latest docker image if you are using docker for format check)"
157     exit 1
158   fi
159   for DIR_CLANG_FORMAT_8 in $(git ls-files -co --exclude-standard '*/.clang-format'); do
160     DIRECTORIES_USE_CLANG_FORMAT_8+=($(dirname "${DIR_CLANG_FORMAT_8}"))
161   done
162
163   # Check c++ files
164   FILES_TO_CHECK_CPP=()
165   FILES_TO_CHECK_CPP_BY_CLANG_FORMAT_8=()
166   for f in ${FILES_TO_CHECK[@]}; do
167     # Manually ignore style checking
168     if [[ ${f} == +(*/NeuralNetworks.h|*/NeuralNetworksExtensions.h) ]]; then
169       continue
170     fi
171
172     # File extension to check
173     if [[ ${f} == +(*.h|*.hpp|*.cpp|*.cc|*.c|*.cl) ]]; then
174
175       # Check clang-format-8 target files first
176       # TODO Remove this after migration to clang-format-8
177       FOUND_CLANG_8=0
178       for USE_CLANG_FORMAT_8 in ${DIRECTORIES_USE_CLANG_FORMAT_8[@]}; do
179         if [[ $f = $USE_CLANG_FORMAT_8* ]]; then
180           FILES_TO_CHECK_CPP_BY_CLANG_FORMAT_8+=("$f")
181           FOUND_CLANG_8=1
182           break
183         fi
184       done
185
186       if [[ $FOUND_CLANG_8 -ne 1 ]]; then
187         FILES_TO_CHECK_CPP+=("${f}")
188       fi
189     fi
190   done
191
192   # Skip by '.FORMATDENY' file
193   for s in ${DIRECTORIES_NOT_TO_BE_TESTED[@]}; do
194     FILES_TO_CHECK_CPP=(${FILES_TO_CHECK_CPP[*]/$s*/})
195     FILES_TO_CHECK_CPP_BY_CLANG_FORMAT_8=(${FILES_TO_CHECK_CPP_BY_CLANG_FORMAT_8[*]/$s*/})
196   done
197
198   if [[ ${#FILES_TO_CHECK_CPP} -ne 0 ]]; then
199     ${CLANG_FORMAT} -i ${FILES_TO_CHECK_CPP[@]}
200     EXIT_CODE=$?
201     if [[ ${EXIT_CODE} -ne 0 ]]; then
202       INVALID_EXIT=${EXIT_CODE}
203     fi
204   fi
205
206   # Check by clang-format-8
207   # TODO Remove this after migration to clang-format-8
208   if [[ ${#FILES_TO_CHECK_CPP_BY_CLANG_FORMAT_8} -ne 0 ]]; then
209     ${CLANG_FORMAT_8} -i ${FILES_TO_CHECK_CPP_BY_CLANG_FORMAT_8[@]}
210     EXIT_CODE=$?
211     if [[ ${EXIT_CODE} -ne 0 ]]; then
212       INVALID_EXIT=${EXIT_CODE}
213     fi
214   fi
215 }
216
217 function check_python_files() {
218   if [[ ${__Check_PYTHON} -eq 0 ]]; then
219     echo "[SKIPPED] Python check is skipped"
220     return
221   fi
222
223   if ! command_exists yapf; then
224     echo "[ERROR] yapf is unavailable"
225     echo "       Please install yapf."
226     exit 1
227   fi
228
229   # Check python files
230   FILES_TO_CHECK_PYTHON=()
231   for f in ${FILES_TO_CHECK[@]}; do
232     # File extension to check
233     if [[ ${f} == *.py ]]; then
234       FILES_TO_CHECK_PYTHON+=("${f}")
235     fi
236     # Exceptional case: one-cmds don't have '.py' extension
237     if [[ ${f} == compiler/one-cmds/* ]]; then
238       # Ignore non-python source (cmake, etc)
239       # Ignore shell script: one-prepare-venv
240       if [[ ${f} != compiler/one-cmds/*.* ]] && [[ ${f} != compiler/one-cmds/one-prepare-venv ]]; then
241         FILES_TO_CHECK_PYTHON+=("${f}")
242       fi
243     fi
244   done
245   for s in ${DIRECTORIES_NOT_TO_BE_TESTED[@]}; do
246     skip=${s#'.'/}/
247     FILES_TO_CHECK_PYTHON=(${FILES_TO_CHECK_PYTHON[*]/$skip*/})
248   done
249
250   if [[ ${#FILES_TO_CHECK_PYTHON} -ne 0 ]]; then
251     yapf -i ${FILES_TO_CHECK_PYTHON[@]}
252     EXIT_CODE=$?
253     if [[ ${EXIT_CODE} -ne 0 ]]; then
254       INVALID_EXIT=${EXIT_CODE}
255     fi
256   fi
257 }
258
259 pushd ${NNAS_PROJECT_PATH}
260
261 if [[ -n "$(git diff)" ]] && { [[ "${CHECK_DIFF_ONLY}" != "1" ]] || [[ "${CHECK_STAGED_ONLY}" != "1" ]]; }; then
262   echo "[WARNING] Commit all the changes before running format check"
263   echo "          ${PATCH_FILE} file will contain unstaged files"
264 fi
265
266 __Check_CPP=${CHECK_CPP:-"1"}
267 __Check_PYTHON=${CHECK_PYTHON:-"1"}
268
269 FILES_TO_CHECK=$(git ls-files -c --exclude-standard ${DIRECTORIES_TO_BE_TESTED[@]})
270 if [[ "${CHECK_DIFF_ONLY}" = "1" ]]; then
271   MASTER_EXIST=$(git rev-parse --verify master)
272   CURRENT_BRANCH=$(git branch | grep \* | cut -d ' ' -f2-)
273   DIFF_COMMITS=`git log --graph --oneline master..HEAD | wc -l`
274   if [[ -z "${MASTER_EXIST}" ]]; then
275     echo "Cannot found local master branch"
276   elif [[ "${CURRENT_BRANCH}" = "master" ]]; then
277     echo "Current branch is master"
278   else
279     if [[ "${CHECK_STAGED_ONLY}" = "1" ]]; then
280       FILES_TO_CHECK=$(git diff --staged --name-only --diff-filter=d)
281     else
282       FILES_TO_CHECK=$(git diff --name-only --diff-filter=d HEAD~${DIFF_COMMITS})
283     fi
284   fi
285 fi
286
287 for DIR_NOT_TO_BE_TESTED in $(git ls-files -co --exclude-standard '*/.FORMATDENY'); do
288   DIRECTORIES_NOT_TO_BE_TESTED+=($(dirname "${DIR_NOT_TO_BE_TESTED}"))
289 done
290
291 exclude_symbolic_links
292 check_newline
293 check_permission
294 check_cpp_files
295 check_python_files
296
297 if [[ "${CHECK_DIFF_ONLY}" = "1" ]] && [[ "${CHECK_STAGED_ONLY}" = "1" ]]; then
298   if [[ ! -z "${FILES_TO_CHECK}" ]]; then
299     DIFF=$(git diff ${FILES_TO_CHECK} | tee ${PATCH_FILE})
300   fi
301 else
302   DIFF=$(git diff | tee ${PATCH_FILE})
303 fi
304
305 popd
306
307 if [[ -z "${CRCHECK}" ]] && [[ ! -n "${DIFF}" ]] && [[ ${INVALID_EXIT} -eq 0 ]]; then
308   echo "[PASSED] Format checker succeed."
309   return
310 fi
311
312 # Something went wrong
313
314 if [[ ! -z "${CRCHECK}" ]]; then
315   echo "[FAILED] Please use LF for newline for following files."
316   echo "${CRCHECK}"
317 fi
318
319 if [[ -s ${PATCH_FILE} ]]; then
320   echo "[FAILED] Format checker failed and update code to follow convention."
321   echo "         You can find changes in ${PATCH_FILE}"
322 fi
323
324 if [[ ${INVALID_EXIT} -ne 0 ]]; then
325   echo "[[FAILED] Invalid format checker exit."
326 fi
327
328 exit 1