From 23bc2df6a7c5879487dee97bbda8a3238077a3c0 Mon Sep 17 00:00:00 2001 From: Whisperity Date: Sun, 28 Nov 2021 15:50:11 +0100 Subject: [PATCH] Add the `parse` step that converts and shows analysis results --- .github/workflows/test.yml | 55 +++++++++++++++++++++++++++++---- README.md | 36 ++++++++++++++++++--- action.yml | 32 +++++++++++++++++++ src/apt-dependencies.sh | 2 +- src/execute-analysis.sh | 6 +++- src/get-or-create-build-json.sh | 2 +- src/parse-results.sh | 54 ++++++++++++++++++++++++++++++++ 7 files changed, 174 insertions(+), 13 deletions(-) create mode 100755 src/parse-results.sh diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ae29fb4..11f5139 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,10 +2,14 @@ name: 'Action test' on: pull_request: + paths-ignore: + - '**.md' push: branches: - master - 'releases/*' + paths-ignore: + - '**.md' jobs: # Fetch tests do not produce results, but test the functionality of the @@ -43,20 +47,26 @@ jobs: name: "Simple analysis: ${{ matrix.logfile && 'logfile' || 'no logfile' }}, ${{ matrix.build-command && 'build-command' || 'no build-command' }}, ${{ matrix.analyze-output && 'analyze-output' || 'no analyze-output'}}" runs-on: ubuntu-20.04 - # Allow continuing the build, we check "expected failure" for misconfiguration. - continue-on-error: ${{ (matrix.logfile != '') == (matrix.build-command != '') }} steps: - uses: actions/checkout@v2 - run: test/fix_compile_json_paths.sh - uses: ./ + id: codechecker + # Allow continuing the build, we check "expected failure" for misconfiguration. + continue-on-error: ${{ (matrix.logfile != '') == (matrix.build-command != '') }} with: logfile: ${{ matrix.logfile }} build-command: ${{ matrix.build-command }} analyze-output: ${{ matrix.analyze-output }} + - name: "Reject test if previous step did not fail" + if: ${{ steps.codechecker.continue-on-error && steps.codechecker.outcome != 'failure' }} + run: | + echo "::error title=Step with expected failure passed::" + exit 1 - analysis-cfg: - name: "Analysis: Custom configuration" + analyze-cfg: + name: "Analyze: Custom configuration" runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 @@ -65,8 +75,8 @@ jobs: with: config: 'test/codechecker.verbose.json' logfile: 'test/simple/compile_commands.json' - analysis-ctu: - name: "Analysis: CTU shortcut" + analyze-ctu: + name: "Analyze: CTU shortcut" runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 @@ -75,3 +85,36 @@ jobs: with: logfile: 'test/ctu/compile_commands.json' ctu: true + + fail-on-error: + name: "Parse: Fail the build on error" + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - run: test/fix_compile_json_paths.sh + - uses: ./ + id: codechecker + continue-on-error: true + with: + logfile: 'test/simple/compile_commands.json' + fail-build-if-reports: true + - name: "Reject test if previous step did not fail" + if: ${{ steps.codechecker.outcome != 'failure' }} + run: | + echo "::error title=fail-on-error test passed::Expected the 'parse' step to breka the build." + exit 1 + + parse-html: + name: "Parse: Generate and upload report HTML artefact" + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - run: test/fix_compile_json_paths.sh + - uses: ./ + id: codechecker + with: + logfile: 'test/simple/compile_commands.json' + - uses: actions/upload-artifact@v2 + with: + name: "Parse HTML test results" + path: ${{ steps.codechecker.outputs.result-html-dir }} diff --git a/README.md b/README.md index 09aa226..2717a27 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ This single action composite script encompasses the following steps: 1. Obtain a package of the LLVM Clang suite's analysers, and CodeChecker. 2. _(Optional)_ Log the build commands to prepare for analysis. 3. Execute the analysis. + 4. Show the analysis results in the CI log, and create HTML reports that can be uploaded as an artefact. ℹ️ **Note:** Static analysis can be a time-consuming process. It's recommended that the static analysis step is not sequential with the rest of a CI execution, but either runs as its own job in a workflow, or a completely distinct workflow altogether. @@ -48,8 +49,15 @@ runs: # Run the analysis - uses: whisperity/codechecker-analysis-action + id: codechecker with: logfile: ${{ github.workspace }}/Build/compile_commands.json + + # Upload the results to the CI. + - uses: actions/upload-artifact@v2 + with: + name: "CodeChecker Bug Reports" + path: ${{ steps.codechecker.outputs.result-html-dir }} ``` ### Projects that need to self-creating a *JSON Compilation Database* or require generated code @@ -77,8 +85,15 @@ runs: # Run the analysis - uses: whisperity/codechecker-analysis-action + id: codechecker with: build-command: "cd ${{ github.workspace }}/Build; cmake --build ." + + # Upload the results to the CI. + - uses: actions/upload-artifact@v2 + with: + name: "CodeChecker Bug Reports" + path: ${{ steps.codechecker.outputs.result-html-dir }} ``` @@ -115,12 +130,25 @@ runs: | `analyze-output` | (auto-generated) | The directory where the **raw** analysis output should be stored. | | `ctu` | `false` | Enable [Cross Translation Unit analysis](http://clang.llvm.org/docs/analyzer/user-docs/CrossTranslationUnit.html) in the _Clang Static Analyzer_. ⚠️ **CAUTION!** _CTU_ analysis might take a very long time, and CTU is officially regarded as experimental. | +### Report configuration + +🔖 Read more about [`CodeChecker parse`](http://codechecker.readthedocs.io/en/latest/analyzer/user_guide/#parse) in the official documentation. + +ℹ️ **Note:** Due to static analysis being potentially noisy and the reports being unwieldy to fix, the default behaviour is to only report the findings but do not break the CI. + + +| Variable | Default | Description | +|-------------------------|---------|-----------------------------------------------------------------------------------| +| `fail-build-if-reports` | `false` | If set to `true`, the build will be set to broken if the static analysers report. | + ## Action *`outputs`* to use in further steps The action exposes the following outputs which may be used in a workflow's steps succeeding the analysis. -| Variable | Value | Description | -|------------------|-------------------------------------------|----------------------------------------------------------------------| -| `logfile` | Auto-generated, or `logfile` input | The JSON Compilation Database of the analysis that was executed. | -| `analyze-output` | Auto-generated, or `analyze-output` input | The directory where the **raw** analysis output files are available. | +| Variable | Value | Description | +|-------------------|-------------------------------------------|-------------------------------------------------------------------------------| +| `analyze-output` | Auto-generated, or `analyze-output` input | The directory where the **raw** analysis output files are available. | +| `logfile` | Auto-generated, or `logfile` input | The JSON Compilation Database of the analysis that was executed. | +| `result-html-dir` | Auto-generated. | The directory where the **user-friendly HTML** bug reports were generated to. | +| `warnings` | `true` or `false` | Whether the static analysers reported any findings. | diff --git a/action.yml b/action.yml index 2cc9d04..3711ae6 100644 --- a/action.yml +++ b/action.yml @@ -39,6 +39,11 @@ inputs: default: 'false' required: true + fail-build-if-reports: + description: 'Whether to fail the build if static analysis warnings are emitted.' + default: 'false' + required: true + outputs: logfile: description: 'The location of the JSON Compilation Database that was used for the analysis.' @@ -48,6 +53,13 @@ outputs: description: 'The output directory where the raw analysis output was stored to.' value: ${{ steps.analyze.outputs.OUTPUT_DIR }} + warnings: + description: 'Whether the static analyser(s) reported any findings.' + value: ${{ steps.parse.outputs.HAS_FINDINGS }} + result-html-dir: + description: 'The output directory where the user-friendly HTML reports were stored to.' + value: ${{ steps.parse.outputs.HTML_DIR }} + runs: using: "composite" steps: @@ -134,3 +146,23 @@ runs: IN_OUTPUT_DIR: ${{ inputs.analyze-output }} shell: bash run: ${{ github.action_path }}/src/execute-analysis.sh + + - name: "Parse and convert results" + id: parse + env: + PROJECT_PATH: ${{ github.workspace }} + CODECHECKER_PATH: ${{ steps.codechecker.outputs.PATH }} + RAW_RESULT_DIR: ${{ steps.analyze.outputs.OUTPUT_DIR }} + + IN_CONFIGFILE: ${{ inputs.config }} + IN_FAIL_IF_REPORTS: ${{ inputs.fail-build-if-reports }} + IN_OUTPUT_DIR: ${{ inputs.analyze-output }} + shell: bash + run: ${{ github.action_path }}/src/parse-results.sh + + - name: "Fail the build if requested and warnings detected" + if: ${{ steps.parse.outputs.HAS_FINDINGS == 'true' && inputs.fail-build-if-reports == 'true' }} + shell: bash + run: | + echo "Static analysis reported warnings, and user requested build breaking." + exit 1 diff --git a/src/apt-dependencies.sh b/src/apt-dependencies.sh index f29a2c1..171b43e 100755 --- a/src/apt-dependencies.sh +++ b/src/apt-dependencies.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -ex +set -x sudo apt-get -y --no-install-recommends install \ build-essential \ diff --git a/src/execute-analysis.sh b/src/execute-analysis.sh index 312d3cd..bbd02aa 100755 --- a/src/execute-analysis.sh +++ b/src/execute-analysis.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -ex +set -x if [[ -z "$COMPILATION_DATABASE" ]]; then echo "::error title=Internal error::environment variable 'COMPILATION_DATABASE' missing!" @@ -25,6 +25,10 @@ if [[ "$IN_CTU" == "true" ]]; then echo "::notice title=Cross Translation Unit analyis::CTU has been enabled, the analysis might take a long time!" fi +"$CODECHECKER_PATH"/CodeChecker analyzers \ + --detail \ + || true + # Note: Ignoring the result of the analyze command in CTU mode, as we do not # wish to break the build on a CTU failure. diff --git a/src/get-or-create-build-json.sh b/src/get-or-create-build-json.sh index 7804a2f..78dedcc 100755 --- a/src/get-or-create-build-json.sh +++ b/src/get-or-create-build-json.sh @@ -1,5 +1,5 @@ #!/bin/bash -set -ex +set -x if [[ ! -z "$IN_LOGFILE" && ! -z "$IN_COMMAND" ]]; then echo "::error title=Configuration error::'logfile' and 'build-command' both specified!" diff --git a/src/parse-results.sh b/src/parse-results.sh new file mode 100755 index 0000000..c5338ee --- /dev/null +++ b/src/parse-results.sh @@ -0,0 +1,54 @@ +#!/bin/bash +set -x + +if [[ -z "$PROJECT_PATH" ]]; then + echo "::error title=Internal error::environment variable 'PROJECT_PATH' missing!" + exit 1 +fi + +if [[ -z "$RAW_RESULT_DIR" ]]; then + echo "::error title=Internal error::environment variable 'RAW_RESULT_DIR' missing!" + exit 1 +fi + +OUTPUT_DIR="$IN_OUTPUT_DIR" +if [[ -z "$OUTPUT_DIR" ]]; then + OUTPUT_DIR=~/"$ACTION_NAME"_Results-HTML +fi + +mkdir -pv "$(dirname $"OUTPUT_DIR")" + +if [[ ! -z "$IN_CONFIGFILE" ]]; then + CONFIG_FLAG_1="--config" + CONFIG_FLAG_2=$IN_CONFIGFILE + echo "Using configuration file \"$IN_CONFIGFILE\"!" +fi + +"$CODECHECKER_PATH"/CodeChecker parse \ + "$RAW_RESULT_DIR" \ + --export "html" \ + --output "$OUTPUT_DIR" \ + --trim-path-prefix "$PROJECT_PATH" \ + || true +echo "::set-output name=HTML_DIR::$OUTPUT_DIR" + +"$CODECHECKER_PATH"/CodeChecker parse \ + "$RAW_RESULT_DIR" \ + --trim-path-prefix "$PROJECT_PATH" +EXIT_CODE=$? + +if [[ "$EXIT_CODE" == "2" ]]; then + echo "::set-output name=HAS_FINDINGS::true" + + if [[ "$IN_FAIL_IF_REPORTS" == "true" ]]; then + echo "::notice title=Static analysis suppressed::CodeChecker static analyser found bug reports, but the build job is configured to suppress it." + fi + + # Let the jobs continue. If there were failures, the action script will break + # the build in a later step. (After a potential upload to server.) + EXIT_CODE=0 +else + echo "::set-output name=HAS_FINDINGS::false" +fi + +exit $EXIT_CODE