From 674c53ea348bfb1123bf0eca7326dbc712a9cf54 Mon Sep 17 00:00:00 2001 From: shenxianpeng Date: Thu, 19 Aug 2021 03:52:52 +0000 Subject: [PATCH 1/2] #8 update readme to suggest open push event by default --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ec947c89..6f2ef85f 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,8 @@ Just create a `yml` file under your GitHub repository. For example `.github/work ```yml name: cpp-linter -on: [pull_request] +# Triggers the workflow on push or pull request events +on: [push, pull_request] jobs: cpp-linter: name: cpp-linter From bdb5877f3d0a80bde81115a0d97f0084c0b9bbe4 Mon Sep 17 00:00:00 2001 From: Brendan <2bndy5@gmail.com> Date: Sun, 22 Aug 2021 00:22:22 -0700 Subject: [PATCH 2/2] Push capable (#14) * Output exit code (#10) * solution for #7 * parameterize EXIT_CODE * fix bash function calls; switch to the new docker image * fix bash array length syntax * oops need that apt update * ok now change the test code a bit * check output is working * fix bad yml syntax * is it being set at all? * use a string instead of an integer * fix bash logic syntax * single square brackets are the old way to do logic * review changes * add output var info to the readme * Update demo.cpp * Revert action to upstream master * fix dockerfile from image not exist * solution for #7 * parameterize EXIT_CODE * fix bash function calls; switch to new docker image * check output is working * show me some env vars * trigger on push events * switch action to push-capable branch * echo the JSON that lives on the runner * use cat instead of echo * remove backticks * use alpine while debugging git events * Revert "use alpine while debugging git events" This reverts commit fbbb1b5a6f8a80f8d4245ebce07d7048ff74fedd. * test push compatibility * provide version input * docker uses tags * oops update run_checks.sh * fix jq args on push * test push src change * test src change w/ GH checkout * fix filename output for push events (w/ checkouts) * test new comment body * gotta break that bad habit * check against src change * use sed directly instead of sed then cat * tidy docs lied * don't know why sed isn't working * try multiple ARG options * try to replace the docker file * maybe without dyn var * bad domain? * nope needs a docker:// domain * reverting changes to docker file FROM * fix clang-format output to file * test on src changes * list clang-fmt warnings per file * add demo.h and adjust comment body * make test action use default extensions * don't checkout repo on test action * stay in working dir. DL into a hidden file * organize output better * adjust output again * fix output; switch back to checkout repo * enforce CPP syntax in the filename (no checkout) * sed taking too much out of the path in clang-tidy out * that's better (checkout is faster) * segregate script into functions * oops uncomment from debugging locally * show me a long diff * show me a split diff * clean-up (ready for PR) * update readme * remove the version from user inputs * only apply clang-tidy checks if it was specified * switch test action back to upstream * add version for user input --- .gitattributes | 8 + .github/workflows/test.yml | 17 +- .gitignore | 5 +- Dockerfile | 12 +- README.md | 9 +- action.yml | 31 +++- demo/.clang-tidy | 186 ++++++++++++++++++++ demo/compile_commands.json | 15 +- demo/demo.cpp | 9 +- demo/demo.hpp | 39 +++++ runchecks.sh | 339 ++++++++++++++++++++++++++++--------- 11 files changed, 569 insertions(+), 101 deletions(-) create mode 100644 .gitattributes create mode 100644 demo/.clang-tidy create mode 100644 demo/demo.hpp diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..e1a14d22 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Set the default behavior, in case people don't have core.autocrlf set. +* text=auto + +# Explicitly declare text files you want to always be normalized and converted +# to native line endings on checkout. +*.py text eol=lf +*.rst text eol=lf +*.sh text eol=lf diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b2d6376b..72418153 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,15 +1,22 @@ name: Test action -on: [pull_request] +on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - # - uses: actions/checkout@v2 + - uses: actions/checkout@v2 - uses: shenxianpeng/cpp-linter-action@master - with: - style: file - extensions: 'cpp' + id: linter env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + style: file + + - name: Fail fast?! + if: steps.linter.outputs.checks-failed > 0 + run: | + echo "Some files failed the linting checks!" + # for actual deployment + # run: exit 1 diff --git a/.gitignore b/.gitignore index 2edf4555..cb8d0354 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ -clang-format-report.txt -clang-tidy-report.txt \ No newline at end of file +.cpp_linter_action_changed_files.json +clang_format_report.txt +clang_tidy_report.txt diff --git a/Dockerfile b/Dockerfile index 204f8cf7..4a74c792 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,7 @@ -FROM ubuntu:latest +FROM xianpengshen/clang-tools:all + +# WORKDIR option is set by the github action to the environment variable GITHUB_WORKSPACE. +# See https://docs.github.com/en/actions/creating-actions/dockerfile-support-for-github-actions#workdir LABEL com.github.actions.name="cpp-linter check" LABEL com.github.actions.description="Lint your code with clang-tidy in parallel to your builds" @@ -8,10 +11,13 @@ LABEL com.github.actions.color="gray-dark" LABEL repository="https://github.com/shenxianpeng/cpp-linter-action" LABEL maintainer="shenxianpeng <20297606+shenxianpeng@users.noreply.github.com>" -# WORKDIR /build RUN apt-get update -RUN apt-get -y install curl clang-tidy cmake jq clang clang-format +RUN apt-get -y install curl jq COPY runchecks.sh /entrypoint.sh RUN chmod +x /entrypoint.sh + +# github action args use the CMD option +# See https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions#runsargs +# also https://docs.docker.com/engine/reference/builder/#cmd ENTRYPOINT [ "/entrypoint.sh" ] diff --git a/README.md b/README.md index 6f2ef85f..2b3a854a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Github Actions for linting the C/C++ code. Integrated clang-tidy, clang-format c Just create a `yml` file under your GitHub repository. For example `.github/workflows/cpp-linter.yml` -!!! Requires `secrets.GITHUB_TOKEN` set to an environment variable name "GITHUB_TOKEN". +!!! Requires `secrets.GITHUB_TOKEN` set to an environment variable named `GITHUB_TOKEN`. ```yml name: cpp-linter @@ -31,6 +31,13 @@ jobs: |------------|---------------|-------------| | style | 'llvm' | The style rules to use. Set this to 'file' to have clang-format use the closest relative .clang-format file. | | extensions | 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx' | The file extensions to run the action against. This is a comma-separated string. | +| tidy-checks | 'boost-*,bugprone-*,performance-*,readability-*,portability-*,modernize-*,clang-analyzer-*,cppcoreguidelines-*' | A string of regex-like patterns specifying what checks clang-tidy will use.| +| repo-root | '.' | The relative path to the repository root directory. This path is relative to path designated by the runner's GITHUB_WORKSPACE environment variable. | +| version | '10' | The desired version of the clang tools to use. Accepted options are strings which can be 6.0, 7, 8, 9, 10, 11, 12. | + +### Outputs + +This action creates 1 output variable named `checks-failed`. Even if the linting checks fail for source files this action will still pass, but users' CI workflows can use this action's output to exit the workflow early if that is desired. ## Results of GitHub Actions diff --git a/action.yml b/action.yml index f50f994f..066a7ad0 100644 --- a/action.yml +++ b/action.yml @@ -5,17 +5,42 @@ branding: icon: 'check-circle' color: 'green' inputs: - style: # the specific style rules - description: "The style rules to use (defaults to 'llvm'). Set this to 'file' to have clang-format use the closest relative .clang-format file." + style: + description: > + The style rules to use (defaults to 'llvm'). + Set this to 'file' to have clang-format use the closest relative .clang-format file. required: false default: 'llvm' extensions: - description: "The file extensions to run the action against. This comma-separated string defaults to 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx'." + description: > + The file extensions to run the action against. + This comma-separated string defaults to 'c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx'. required: false default: "c,h,C,H,cpp,hpp,cc,hh,c++,h++,cxx,hxx" + tidy-checks: + description: > + A string of regex-like patterns specifying what checks clang-tidy will use. + This defaults to 'boost-*,bugprone-*,performance-*,readability-*,portability-*,modernize-*,clang-analyzer-*,cppcoreguidelines-*'. See also clang-tidy docs for more info. + required: false + default: 'boost-*,bugprone-*,performance-*,readability-*,portability-*,modernize-*,clang-analyzer-*,cppcoreguidelines-*' + repo-root: + description: > + The relative path to the repository root directory. The default value '.' is relative to the runner's GITHUB_WORKSPACE environment variable. + required: false + default: '.' + version: + description: "The desired version of the clang tools to use. Accepted options are strings which can be 6.0, 7, 8, 9, 10, 11, 12. Defaults to 10." + required: false + default: '10' +outputs: + checks-failed: + description: An integer that can be used as a boolean value to indicate if all checks failed. runs: using: 'docker' image: 'Dockerfile' args: - ${{ inputs.style }} - ${{ inputs.extensions }} + - ${{ inputs.tidy-checks }} + - ${{ inputs.repo-root }} + - ${{ inputs.version }} diff --git a/demo/.clang-tidy b/demo/.clang-tidy new file mode 100644 index 00000000..d3865ade --- /dev/null +++ b/demo/.clang-tidy @@ -0,0 +1,186 @@ +--- +Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,performance-*,bugprone-*,clang-analyzer-*,mpi-*,misc-*,readability-*' +WarningsAsErrors: '' +HeaderFilterRegex: '' +AnalyzeTemporaryDtors: false +FormatStyle: 'file' +CheckOptions: + - key: bugprone-argument-comment.CommentBoolLiterals + value: '0' + - key: bugprone-argument-comment.CommentCharacterLiterals + value: '0' + - key: bugprone-argument-comment.CommentFloatLiterals + value: '0' + - key: bugprone-argument-comment.CommentIntegerLiterals + value: '0' + - key: bugprone-argument-comment.CommentNullPtrs + value: '0' + - key: bugprone-argument-comment.CommentStringLiterals + value: '0' + - key: bugprone-argument-comment.CommentUserDefinedLiterals + value: '0' + - key: bugprone-argument-comment.IgnoreSingleArgument + value: '0' + - key: bugprone-argument-comment.StrictMode + value: '0' + - key: bugprone-assert-side-effect.AssertMacros + value: assert + - key: bugprone-assert-side-effect.CheckFunctionCalls + value: '0' + - key: bugprone-dangling-handle.HandleClasses + value: 'std::basic_string_view;std::experimental::basic_string_view' + - key: bugprone-dynamic-static-initializers.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: bugprone-exception-escape.FunctionsThatShouldNotThrow + value: '' + - key: bugprone-exception-escape.IgnoredExceptions + value: '' + - key: bugprone-misplaced-widening-cast.CheckImplicitCasts + value: '0' + - key: bugprone-not-null-terminated-result.WantToUseSafeFunctions + value: '1' + - key: bugprone-signed-char-misuse.CharTypdefsToIgnore + value: '' + - key: bugprone-sizeof-expression.WarnOnSizeOfCompareToConstant + value: '1' + - key: bugprone-sizeof-expression.WarnOnSizeOfConstant + value: '1' + - key: bugprone-sizeof-expression.WarnOnSizeOfIntegerExpression + value: '0' + - key: bugprone-sizeof-expression.WarnOnSizeOfThis + value: '1' + - key: bugprone-string-constructor.LargeLengthThreshold + value: '8388608' + - key: bugprone-string-constructor.WarnOnLargeLength + value: '1' + - key: bugprone-suspicious-enum-usage.StrictMode + value: '0' + - key: bugprone-suspicious-missing-comma.MaxConcatenatedTokens + value: '5' + - key: bugprone-suspicious-missing-comma.RatioThreshold + value: '0.200000' + - key: bugprone-suspicious-missing-comma.SizeThreshold + value: '5' + - key: bugprone-suspicious-string-compare.StringCompareLikeFunctions + value: '' + - key: bugprone-suspicious-string-compare.WarnOnImplicitComparison + value: '1' + - key: bugprone-suspicious-string-compare.WarnOnLogicalNotComparison + value: '0' + - key: bugprone-too-small-loop-variable.MagnitudeBitsUpperLimit + value: '16' + - key: bugprone-unhandled-self-assignment.WarnOnlyIfThisHasSuspiciousField + value: '1' + - key: bugprone-unused-return-value.CheckedFunctions + value: '::std::async;::std::launder;::std::remove;::std::remove_if;::std::unique;::std::unique_ptr::release;::std::basic_string::empty;::std::vector::empty' + - key: cert-dcl16-c.NewSuffixes + value: 'L;LL;LU;LLU' + - key: cert-oop54-cpp.WarnOnlyIfThisHasSuspiciousField + value: '0' + - key: cppcoreguidelines-explicit-virtual-functions.IgnoreDestructors + value: '1' + - key: cppcoreguidelines-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic + value: '1' + - key: google-readability-braces-around-statements.ShortStatementLines + value: '1' + - key: google-readability-function-size.StatementThreshold + value: '800' + - key: google-readability-namespace-comments.ShortNamespaceLines + value: '10' + - key: google-readability-namespace-comments.SpacesBeforeComments + value: '2' + - key: misc-definitions-in-headers.HeaderFileExtensions + value: ',h,hh,hpp,hxx' + - key: misc-definitions-in-headers.UseHeaderFileExtension + value: '1' + - key: misc-throw-by-value-catch-by-reference.CheckThrowTemporaries + value: '1' + - key: misc-unused-parameters.StrictMode + value: '0' + - key: modernize-loop-convert.MaxCopySize + value: '16' + - key: modernize-loop-convert.MinConfidence + value: reasonable + - key: modernize-loop-convert.NamingStyle + value: CamelCase + - key: modernize-pass-by-value.IncludeStyle + value: llvm + - key: modernize-replace-auto-ptr.IncludeStyle + value: llvm + - key: modernize-use-nullptr.NullMacros + value: 'NULL' + - key: performance-faster-string-find.StringLikeClasses + value: 'std::basic_string' + - key: performance-for-range-copy.AllowedTypes + value: '' + - key: performance-for-range-copy.WarnOnAllAutoCopies + value: '0' + - key: performance-inefficient-string-concatenation.StrictMode + value: '0' + - key: performance-inefficient-vector-operation.EnableProto + value: '0' + - key: performance-inefficient-vector-operation.VectorLikeClasses + value: '::std::vector' + - key: performance-move-const-arg.CheckTriviallyCopyableMove + value: '1' + - key: performance-move-constructor-init.IncludeStyle + value: llvm + - key: performance-no-automatic-move.AllowedTypes + value: '' + - key: performance-type-promotion-in-math-fn.IncludeStyle + value: llvm + - key: performance-unnecessary-copy-initialization.AllowedTypes + value: '' + - key: performance-unnecessary-value-param.AllowedTypes + value: '' + - key: performance-unnecessary-value-param.IncludeStyle + value: llvm + - key: readability-braces-around-statements.ShortStatementLines + value: '0' + - key: readability-else-after-return.WarnOnUnfixable + value: '1' + - key: readability-function-size.BranchThreshold + value: '4294967295' + - key: readability-function-size.LineThreshold + value: '4294967295' + - key: readability-function-size.NestingThreshold + value: '4294967295' + - key: readability-function-size.ParameterThreshold + value: '4294967295' + - key: readability-function-size.StatementThreshold + value: '800' + - key: readability-function-size.VariableThreshold + value: '4294967295' + - key: readability-identifier-naming.IgnoreFailedSplit + value: '0' + - key: readability-implicit-bool-conversion.AllowIntegerConditions + value: '0' + - key: readability-implicit-bool-conversion.AllowPointerConditions + value: '0' + - key: readability-inconsistent-declaration-parameter-name.IgnoreMacros + value: '1' + - key: readability-inconsistent-declaration-parameter-name.Strict + value: '0' + - key: readability-magic-numbers.IgnoredFloatingPointValues + value: '1.0;100.0;' + - key: readability-magic-numbers.IgnoredIntegerValues + value: '1;2;3;4;' + - key: readability-redundant-member-init.IgnoreBaseInCopyConstructors + value: '0' + - key: readability-redundant-smartptr-get.IgnoreMacros + value: '1' + - key: readability-redundant-string-init.StringNames + value: '::std::basic_string' + - key: readability-simplify-boolean-expr.ChainedConditionalAssignment + value: '0' + - key: readability-simplify-boolean-expr.ChainedConditionalReturn + value: '0' + - key: readability-simplify-subscript-expr.Types + value: '::std::basic_string;::std::basic_string_view;::std::vector;::std::array' + - key: readability-static-accessed-through-instance.NameSpecifierNestingThreshold + value: '3' + - key: readability-uppercase-literal-suffix.IgnoreMacros + value: '1' + - key: readability-uppercase-literal-suffix.NewSuffixes + value: '' +... diff --git a/demo/compile_commands.json b/demo/compile_commands.json index 2643be40..d782c455 100644 --- a/demo/compile_commands.json +++ b/demo/compile_commands.json @@ -1,7 +1,12 @@ [ -{ - "directory": ".", - "command": "/usr/bin/g++ -Wall -Werror demo.cpp", - "file": "/demo.cpp" -} + { + "directory": ".", + "command": "/usr/bin/g++ -Wall -Werror demo.cpp", + "file": "/demo.cpp" + }, + { + "directory": ".", + "command": "/usr/bin/g++ -Wall -Werror demo.cpp", + "file": "/demo.hpp" + } ] diff --git a/demo/demo.cpp b/demo/demo.cpp index c003979f..8e2c15f4 100644 --- a/demo/demo.cpp +++ b/demo/demo.cpp @@ -1,4 +1,5 @@ - +/** This is a very ugly test code (doomed to fail linting) */ +#include "demo.hpp" #include @@ -6,10 +7,10 @@ int main(){ - printf("Hello world!\n"); + for (;;) break; - return 0;} + printf("Hello world!\n"); -/* This is an ugly test code */ + return 0;} diff --git a/demo/demo.hpp b/demo/demo.hpp new file mode 100644 index 00000000..0bf1104a --- /dev/null +++ b/demo/demo.hpp @@ -0,0 +1,39 @@ +#pragma once + + + +class Dummy { + char* useless; + int numb; + Dummy() :numb(0), useless("\0"){} + + public: + void *not_usefull(char *str){useless = str;} +}; + + + + + + + + + + + + + + + + + + + + + + +struct LongDiff +{ + long diff; + +}; diff --git a/runchecks.sh b/runchecks.sh index f2e901dc..44eaf625 100644 --- a/runchecks.sh +++ b/runchecks.sh @@ -1,85 +1,268 @@ #!/bin/bash -if [[ -z "$GITHUB_TOKEN" ]]; then - echo "The GITHUB_TOKEN is required." - exit 1 -fi +# global varibales +EXIT_CODE="0" +PAYLOAD_TIDY="" +FENCES=$'\n```\n' +OUTPUT="" +URLS="" +PATHNAMES="" +declare -a JSON_INDEX +# alias CLI args args=("$@") FMT_STYLE=${args[0]} IFS=',' read -r -a FILE_EXT_LIST <<< "${args[1]}" +TIDY_CHECKS="${args[2]}" +cd "${args[3]}" || exit "1" +CLANG_VERSION="${args[4]}" -FILES_LINK=`jq -r '.pull_request._links.self.href' "$GITHUB_EVENT_PATH"`/files -echo "Files = $FILES_LINK" - -curl $FILES_LINK > files.json -FILES_URLS_STRING=`jq -r '.[].raw_url' files.json` - -readarray -t URLS <<<"$FILES_URLS_STRING" - -# exclude undesired files -for index in "${!URLS[@]}" -do - is_supported=0 - for i in "${FILE_EXT_LIST[@]}" - do - if [[ ${URLS[index]} == *".$i" ]] - then - is_supported=1 - fi - done - if [ $is_supported == 0 ] - then - unset -v "URLS[index]" - fi -done - -echo "File names: ${URLS[*]}" -mkdir files -cd files -for i in "${URLS[@]}" -do - echo "Downloading $i" - curl -LOk --remote-name $i -done - -echo "Files downloaded!" -echo "Performing checkup:" -clang-tidy --version - -for i in "${URLS[@]}" -do - filename=`basename $i` - clang-tidy $filename -checks=boost-*,bugprone-*,performance-*,readability-*,portability-*,modernize-*,clang-analyzer-cplusplus-*,clang-analyzer-*,cppcoreguidelines-* >> clang-tidy-report.txt - clang-format -style="$FMT_STYLE" --dry-run -Werror "$filename" || echo "File: $filename not formatted!" >> clang-format-report.txt -done - -PAYLOAD_TIDY=`cat clang-tidy-report.txt` -PAYLOAD_FORMAT=`cat clang-format-report.txt` -COMMENTS_URL=$(cat $GITHUB_EVENT_PATH | jq -r .pull_request.comments_url) - -echo $COMMENTS_URL -echo "Clang-tidy errors:" -echo $PAYLOAD_TIDY -echo "Clang-format errors:" -echo $PAYLOAD_FORMAT - -if [ "$PAYLOAD_TIDY" != "" ]; then - OUTPUT=$'**CLANG-TIDY WARNINGS**:\n' - OUTPUT+=$'\n```\n' - OUTPUT+="$PAYLOAD_TIDY" - OUTPUT+=$'\n```\n' -fi - -if [ "$PAYLOAD_FORMAT" != "" ]; then - OUTPUT=$'**CLANG-FORMAT WARNINGS**:\n' - OUTPUT+=$'\n```\n' - OUTPUT+="$PAYLOAD_FORMAT" - OUTPUT+=$'\n```\n' -fi - -echo "OUTPUT is: \n $OUTPUT" - -PAYLOAD=$(echo '{}' | jq --arg body "$OUTPUT" '.body = $body') - -curl -s -S -H "Authorization: token $GITHUB_TOKEN" --header "Content-Type: application/vnd.github.VERSION.text+json" --data "$PAYLOAD" "$COMMENTS_URL" + +################################################### +# Set the exit code (for expected exit calls). +# Optional parameter overides action-specific logic +################################################### +set_exit_code () { + if [[ $# -gt 0 ]] + then + EXIT_CODE="$1" + else + if [[ "$PAYLOAD_FORMAT" != "" || "$PAYLOAD_TIDY" != "" ]] + then + EXIT_CODE="1" + fi + fi + echo "::set-output name=checks-failed::$EXIT_CODE" +} + +################################################### +# Fetch JSON of event's changed files +################################################### +get_list_of_changed_files() { + echo "GH_EVENT_PATH = $GITHUB_EVENT_PATH" + echo "processing $GITHUB_EVENT_NAME event" + # cat "$GITHUB_EVENT_PATH" | jq '.' + + # Use git REST API payload + if [[ "$GITHUB_EVENT_NAME" == "push" ]] + then + FILES_LINK="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/commits/$GITHUB_SHA" + elif [[ "$GITHUB_EVENT_NAME" == "pull_request" ]] + then + # FILES_LINK="$GITHUB_API_URL/repos/$GITHUB_REPOSITORY/pulls//files" + # Get PR ID number from the event's JSON located in the runner's GITHUB_EVENT_PATH + FILES_LINK="$(jq -r '.pull_request._links.self.href' "$GITHUB_EVENT_PATH")/files" + fi + + # Download files list (another JSON containing files' names, URLS, statuses, & diffs/patches) + echo "Fetching files list from $FILES_LINK" + curl "$FILES_LINK" > .cpp_linter_action_changed_files.json +} + +################################################### +# extract info from downloaded JSON file +################################################### +extract_changed_files_info() { + # pull_request events have a slightly different JSON format than push events + JSON_FILES=".[" + if [[ "$GITHUB_EVENT_NAME" == "push" ]] + then + JSON_FILES=".files[" + fi + FILES_URLS_STRING=$(jq -r "$JSON_FILES].raw_url" .cpp_linter_action_changed_files.json) + FILES_NAMES_STRING=$(jq -r "$JSON_FILES].filename" .cpp_linter_action_changed_files.json) + + # convert json info to arrays + readarray -t URLS <<<"$FILES_URLS_STRING" + readarray -t PATHNAMES <<<"$FILES_NAMES_STRING" + + # Initialize the `JSON_INDEX` array. This helps us keep track of the + # source files' index in the JSON after calling `filter_out_source_files()` function. + for index in "${!URLS[@]}" + do + # this will only be used when parsing diffs from the JSON + JSON_INDEX[$index]=$index + done +} + +################################################### +# exclude undesired files (specified by the user) +################################################### +filter_out_non_source_files() { + for index in "${!URLS[@]}" + do + is_supported=0 + for i in "${FILE_EXT_LIST[@]}" + do + if [[ ${URLS[index]} == *".$i" ]] + then + is_supported=1 + break + fi + done + + if [ $is_supported == 0 ] + then + unset -v "URLS[index]" + unset -v "PATHNAMES[index]" + unset -v "JSON_INDEX[index]" + fi + done + + # exit early if nothing to do + if [ ${#URLS[@]} == 0 ] + then + set_exit_code "0" + echo "No source files need checking!" + exit $EXIT_CODE + else + echo "File names: ${PATHNAMES[*]}" + fi +} + +################################################### +# Download the files if not present. +# This function assumes that the working directory is the root of the invoking repo. +# Note that all github actions are run in path specified by the environment variable GITHUB_WORKSPACE. +################################################### +verify_files_are_present() { + # URLS, PATHNAMES, & PATCHES are parallel arrays + for index in "${!PATHNAMES[@]}" + do + if [[ ! -f "${PATHNAMES[index]}" ]] + then + echo "Downloading ${URLS[index]}" + curl --location --insecure --remote-name "${URLS[index]}" + fi + done +} + +################################################### +# get the patch info from the JSON. +# required parameter is the index in the JSON_INDEX array +################################################### +get_patch_info() { + # patches are multiline strings. Thus, they need special attention because of the '\n' used within. + # + # a git diff (aka "patch" in the REST API) can have multiple "hunks" for a single file. + # hunks start with `@@ -, +, @@` + # A positive sign indicates the incoming changes, while a negative sign indicates existing code that was changed + # Any changed lines will also have a prefixed `-` or `+`. + + file_status=$(jq -r "$JSON_FILES${JSON_INDEX[$1]}].status" .cpp_linter_action_changed_files.json) + + # we only need the first line stating the line numbers changed (ie "@@ -1,5 +1,5 @@"") + patched_lines=$(jq -r -c "$JSON_FILES${JSON_INDEX[$1]}].patch" .cpp_linter_action_changed_files.json) + patches=$(echo "$patched_lines" | grep -o "@@ \\-[1-9]*,[1-9]* +[1-9]*,[1-9]* @@" | grep -o " +[1-9]*,[1-9]*" | tr -d "\\n" | sed 's; +;;; s;+;;g') + + # if there is no patch field, we need to handle 'renamed' as an edgde case + if [[ "$patches" == "" ]] + then + echo "${PATHNAMES[$1]} was $file_status" + # don't bother checking renamed files with no changes to file's content + patches="0,0" + fi + echo "$patches" +} + +################################################### +# execute clang-tidy/format & assemble a unified OUTPUT +################################################### +capture_clang_tools_output() { + clang-tidy --version + + for index in "${!URLS[@]}" + do + filename=$(basename ${URLS[index]}) + if [[ -f "${PATHNAMES[index]}" ]] + then + filename="${PATHNAMES[index]}" + fi + + true > clang_format_report.txt + true > clang_tidy_report.txt + + echo "Performing checkup on $filename" + # echo "incoming changed lines: $(get_patch_info $index)" + + if [ "$TIDY_CHECKS" == "" ] + then + clang-tidy-"$CLANG_VERSION" "$filename" >> clang_tidy_report.txt + else + clang-tidy-"$CLANG_VERSION" -checks="$TIDY_CHECKS" "$filename" >> clang_tidy_report.txt + fi + clang-format-"$CLANG_VERSION" -style="$FMT_STYLE" --dry-run "$filename" 2> clang_format_report.txt + + if [[ $(wc -l < clang_tidy_report.txt) -gt 0 ]] + then + PAYLOAD_TIDY+=$"### ${PATHNAMES[index]}" + PAYLOAD_TIDY+="$FENCES" + sed -i "s|$GITHUB_WORKSPACE/||g" clang_tidy_report.txt + # cat clang_tidy_report.txt + PAYLOAD_TIDY+=$(cat clang_tidy_report.txt) + PAYLOAD_TIDY+="$FENCES" + fi + + if [[ $(wc -l < clang_format_report.txt) -gt 0 ]] + then + if [ "$OUTPUT" == "" ] + then + OUTPUT=$'## Run `clang-format` on the following files\n' + fi + OUTPUT+="- [ ] ${PATHNAMES[index]}"$'\n' + fi + done + + if [ "$PAYLOAD_TIDY" != "" ]; then + OUTPUT+=$'\n---\n## Output from `clang-tidy`\n' + OUTPUT+="$PAYLOAD_TIDY" + fi + + echo "OUTPUT is:" + echo "$OUTPUT" +} + +################################################### +# POST action's results using REST API +################################################### +post_results() { + # check for access token (ENV VAR needed for git API calls) + if [[ -z "$GITHUB_TOKEN" ]] + then + set_exit_code "1" + echo "The GITHUB_TOKEN is required." + exit "$EXIT_CODE" + fi + + COMMENTS_URL=$(jq -r .pull_request.comments_url "$GITHUB_EVENT_PATH") + if [[ "$GITHUB_EVENT_NAME" == "push" ]] + then + COMMENTS_URL="$FILES_LINK/comments" + fi + + echo "COMMENTS_URL = $COMMENTS_URL" + + PAYLOAD=$(echo '{}' | jq --arg body "$OUTPUT" '.body = $body') + + # creating PR comments is the same API as creating issue. Creating commit comments have more optional parameters (but same required API) + curl -s -S -H "Authorization: token $GITHUB_TOKEN" --header "Content-Type: application/vnd.github.VERSION.text+json" "$COMMENTS_URL" --data "$PAYLOAD" +} + +################################################### +# The main body of this script (all function calls) +################################################### +# for local testing (without docker): +# 1. Set the env var GITHUB_EVENT_NAME to "push" or "pull_request" +# 2. Download and save the event's payload (in JSON) to a file named ".cpp_linter_action_changed_files.json". +# See the FILES_LINK variable in the get_list_of_changed_files() function for the event's payload. +# 3. Comment out the following calls to `get_list_of_changed_files` & `post_results` functions +# 4. Run this script using `./run_checks.sh