diff --git a/.github/workflows/static-test.yml b/.github/workflows/static-test.yml index 7d17466598fc..4529dc39dcc1 100644 --- a/.github/workflows/static-test.yml +++ b/.github/workflows/static-test.yml @@ -29,6 +29,7 @@ jobs: run: | docker run --rm \ -e CI_BASE_BRANCH=master_upstream \ + -e GITHUB_RUN_ID=${GITHUB_RUN_ID} \ -v $(pwd):/data/riotbuild \ riot/riotbuild:latest \ make static-test diff --git a/README.md b/README.md index cb21b66be5f3..ac4ef0512919 100644 --- a/README.md +++ b/README.md @@ -34,7 +34,7 @@ ZZZZZZZZZZZ Z ZZZZZ -The friendly Operating System for IoT! +The friendly Operating System for IoT! RIOT is a real-time multi-threading operating system that supports a range of devices that are typically found in the Internet of Things (IoT): diff --git a/dist/tools/ci/README.md b/dist/tools/ci/README.md new file mode 100644 index 000000000000..cb02e3e78f4a --- /dev/null +++ b/dist/tools/ci/README.md @@ -0,0 +1,72 @@ +CI specific scripts +=================== + +Static tests +------------ + +The [static_tests.sh] script runs all static tests and outputs them in a unified +output. You can also run this script by running `make static-test` in the base +directory. + +To add a new static test, just write a `check.sh` script or call your static +checker directly and add it to the list of static tests in [static_tests.sh]: + +```sh +ENV1=foobar ENV2=snafu run --args +``` + +Github annotations +------------------ +Using [github_annotate.sh] you can generate [Github annotations] for your +tests. You can generate both warnings and errors. Before doing anything however, +you include [github_annotate.sh]\ (assuming `${RIOTTOOLS is set to}` [dist/tools/]) + +```sh +. "${RIOTTOOLS}"/ci/github_annotate.sh +``` + +and set-up the mechanism + +```sh +github_annotate_setup +``` + +If your tests generate output you now can pipe that to the file `${LOGFILE}` by +using `${LOG}`, e.g. + +```sh +my_awesome_test | ${LOG} +``` + +Don't worry, your test will function normal still, if you don't run it in a +Github Action! + +Now you can use `github_annotate_error` and `github_annotate_warning` to +generate the actual errors and warnings. Both commands expect 3 parameters: +1. `FILENAME`: The name of the file the warning or error occurred in, +2. `LINENUM`: The number of the line the warning or error occurred in, and +3. `DETAILS`: Some descriptive details or the message of your warning or error. + +You can parse those from `${LOGFILE}` (e.g. using tools such as `grep`, `sed`, +or `awk`) or generate them on the fly, if your script allows for that. E.g. + +```sh +cat ${LOGFILE} | grep '^.*:[0-9]\+:' | while read error; do + github_annotate_error \ + $(echo "${error}" | cut -d: -f1) \ + $(echo "${error}" | cut -d: -f2) \ + $(echo "${error}" | cut -d: -f3-) +done +``` + +After all errors or warnings are annotated, call `github_annotate_teardown` to +finish annotations. + +**Note:** `github_annotate_report_last_run` is called within [static_tests.sh] +to attach the actual annotations to your PR. You don't need to call it from +within your test if you are adding that test to [static_tests.sh]. + +[static_tests.sh]: ./static_tests.sh +[Github annotations]: https://github.blog/2018-12-14-introducing-check-runs-and-annotations/ +[github_annotate.sh]: ./github_annotate.sh +[dist/tools]: ../ diff --git a/dist/tools/ci/github_annotate.sh b/dist/tools/ci/github_annotate.sh new file mode 100644 index 000000000000..cd834d37d746 --- /dev/null +++ b/dist/tools/ci/github_annotate.sh @@ -0,0 +1,67 @@ +# Copyright 2020 Martine S. Lenders +# +# This file is subject to the terms and conditions of the GNU Lesser +# General Public License v2.1. See the file LICENSE in the top level +# directory for more details. + +LOG=cat +LOGFILE= +OUTFILE=github_annotate_outfile.log +ECHO_ESC=echo + +if ps -p $$ | grep -q '\'; then + # workaround when included in bash to escape newlines and carriage returns + # properly in _escape + ECHO_ESC='echo -e' +fi + +github_annotate_setup() { + if [ -n "${GITHUB_RUN_ID}" ]; then + LOGFILE=run-${GITHUB_RUN_ID}.log + LOG="tee -a ${LOGFILE}" + fi +} + +github_annotate_is_on() { + test -n "${LOGFILE}" + return $? +} + +_escape() { + # see https://stackoverflow.com/a/1252191/11921757 + ${ECHO_ESC} "$1" | sed -e ':a' -e 'N' -e '$!ba' \ + -e 's/%/%25/g' -e 's/\r/%0D/g' -e 's/\n/%0A/g' +} + +github_annotate_error() { + if [ -n "${GITHUB_RUN_ID}" ]; then + FILENAME="${1}" + LINENUM="${2}" + DETAILS="$(_escape "${3}")" + echo "::error file=${FILENAME},line=${LINENUM}::${DETAILS}" >> ${OUTFILE} + fi +} + +github_annotate_warning() { + if [ -n "${GITHUB_RUN_ID}" ]; then + FILENAME="${1}" + LINENUM="${2}" + DETAILS="$(_escape "${3}")" + echo "::warning file=${FILENAME},line=${LINENUM}::${DETAILS}" >> ${OUTFILE} + fi +} + +github_annotate_teardown() { + if [ -n "${LOGFILE}" ]; then + rm -f ${LOGFILE} + LOGFILE= + fi +} + +github_annotate_report_last_run() { + if [ -n "${GITHUB_RUN_ID}" -a -f "${OUTFILE}" ]; then + # de-duplicate errors + sort -u ${OUTFILE} >&2 + fi + rm -f ${OUTFILE} +} diff --git a/dist/tools/ci/static_tests.sh b/dist/tools/ci/static_tests.sh index 09a6a3e9e7ea..cfaccd69e469 100755 --- a/dist/tools/ci/static_tests.sh +++ b/dist/tools/ci/static_tests.sh @@ -10,6 +10,8 @@ # directory for more details. # +. $(dirname "$0")/github_annotate.sh + function print_result { local RED="\033[0;31m" local GREEN="\033[0;32m" @@ -47,6 +49,7 @@ function run { (printf "%s\n" "$OUT" | while IFS= read -r line; do printf "\t%s\n" "$line"; done) echo "" fi + github_annotate_report_last_run } RESULT=0 diff --git a/dist/tools/compile_and_test_for_board/compile_and_test_for_board.py b/dist/tools/compile_and_test_for_board/compile_and_test_for_board.py index fadcca0ac164..50139f9ff981 100755 --- a/dist/tools/compile_and_test_for_board/compile_and_test_for_board.py +++ b/dist/tools/compile_and_test_for_board/compile_and_test_for_board.py @@ -108,6 +108,9 @@ MAKE = os.environ.get('MAKE', 'make') + + + class ErrorInTest(Exception): """Custom exception for a failed test. diff --git a/dist/tools/flake8/check.sh b/dist/tools/flake8/check.sh index ac69ed50a1fc..b9a7f4555627 100755 --- a/dist/tools/flake8/check.sh +++ b/dist/tools/flake8/check.sh @@ -6,6 +6,8 @@ # General Public License v2.1. See the file LICENSE in the top level # directory for more details. # +. "$(dirname "$0")/../ci/github_annotate.sh" +github_annotate_setup FLAKE8_CMD="python3 -m flake8" @@ -43,7 +45,19 @@ ${FLAKE8_CMD} --version &> /dev/null || { exit 1 } -ERRORS=$(${FLAKE8_CMD} --config="${RIOTTOOLS}"/flake8/flake8.cfg ${FILES}) +ERRORS=$(${FLAKE8_CMD} --config="${RIOTTOOLS}"/flake8/flake8.cfg ${FILES} | ${LOG}) + +if github_annotate_is_on; then + grep "^.\+:[0-9]\+:" "${LOGFILE}" | while read line; do + FILENAME=$(echo "${line}" | cut -d: -f1) + LINENR=$(echo "${line}" | cut -d: -f2) + DETAILS=$(echo "${line}" | cut -d: -f3- | + sed -e 's/^[ \t]*//' -e 's/[ \t]*$//') + github_annotate_error "${FILENAME}" "${LINENR}" "${DETAILS}" + done +fi + +github_annotate_teardown if [ -n "${ERRORS}" ] then diff --git a/dist/tools/whitespacecheck/check.sh b/dist/tools/whitespacecheck/check.sh index 565ca57f5741..0cced2986607 100755 --- a/dist/tools/whitespacecheck/check.sh +++ b/dist/tools/whitespacecheck/check.sh @@ -6,6 +6,8 @@ # General Public License v2.1. See the file LICENSE in the top level # directory for more details. +. "$(dirname "$0")/../ci/github_annotate.sh" + IGNORE=$(awk '{ printf ":!%s ", $0 }' "$(dirname "$0")/ignore_list.txt") # If no branch but an option is given, unset BRANCH. @@ -23,6 +25,10 @@ else fi fi +# sets +# - LOG to tee output into for later parsing +# - LOGFILE to parse GitHub annotations into +github_annotate_setup # select files to check if [ -z "${BRANCH}" ]; then @@ -30,17 +36,30 @@ if [ -z "${BRANCH}" ]; then fi git -c core.whitespace="tab-in-indent,tabwidth=4" \ - diff --check "$(git merge-base "${BRANCH}" HEAD)" -- *.[ch] ${IGNORE} - + diff --check "$(git merge-base "${BRANCH}" HEAD)" -- *.[ch] ${IGNORE} \ + | ${LOG} RESULT=$? # Git regards any trailing white space except `\n` as an error so `\r` is # checked here, too git -c core.whitespace="trailing-space" \ - diff --check "$(git merge-base "${BRANCH}" HEAD)" -- . ${IGNORE} + diff --check "$(git merge-base "${BRANCH}" HEAD)" -- . ${IGNORE} \ + | ${LOG} TRAILING_RESULT=$? +if github_annotate_is_on; then + grep "^.\+:[0-9]\+:" "${LOGFILE}" | while read line; do + FILENAME=$(echo "${line}" | cut -d: -f1) + LINENR=$(echo "${line}" | cut -d: -f2) + DETAILS=$(echo "${line}" | cut -d: -f3- | + sed -e 's/^[ \t]*//' -e 's/[ \t]*$//') + github_annotate_error "${FILENAME}" "${LINENR}" "${DETAILS}" + done +fi + +github_annotate_teardown + if [ ${TRAILING_RESULT} -ne 0 ] || [ ${RESULT} -ne 0 ] then echo "ERROR: This change introduces new whitespace errors" @@ -48,3 +67,4 @@ then else exit 0 fi +