diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ac74918..2152ea8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -61,7 +61,6 @@ 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 - steps: - uses: actions/checkout@v2 - run: test/fix_compile_json_paths.sh @@ -105,6 +104,29 @@ jobs: - name: "Reject test if previous step did not produce CTU finding" run: cat ${{ steps.codechecker.outputs.result-log }} | grep "Dereference of null pointer" + report-converter: + name: "Report converter: PyLint" + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - name: "Install PyLint" + run: | + sudo apt-get install -y pylint + - name: "Perform static analysis explicitly with PyLint" + run: | + pylint test/report-converter/testpylint.py \ + -f json \ + --exit-zero \ + > ./pylint_reports.json + - uses: ./ + id: codechecker + with: + report-converter: true + original-analyser: 'pylint' + original-analysis-output: './pylint_reports.json' + - name: "Reject test if previous step did not produce findings" + run: cat ${{ steps.codechecker.outputs.result-log }} | grep "Explicit return in __init__" + reports-errors: name: "Parse: Findings are reported" runs-on: ubuntu-20.04 @@ -219,7 +241,7 @@ jobs: runs-on: ubuntu-20.04 if: ${{ github.event_name == 'push' }} env: - CODECHECKER_VERSION: '6.18.1' + CODECHECKER_VERSION: '6.19.1' steps: - uses: actions/checkout@v2 - run: test/fix_compile_json_paths.sh @@ -240,11 +262,11 @@ jobs: name: "Diff: New findings are discovered and reported" runs-on: ubuntu-20.04 env: - CODECHECKER_VERSION: '6.18.1' + CODECHECKER_VERSION: '6.19.1' # This time, we do not need authentication, so test with the official Docker subsystem. services: codechecker-server: - image: 'codechecker/codechecker-web:6.18.1' + image: 'codechecker/codechecker-web:6.19.1' ports: - 8001:8001/tcp steps: diff --git a/README.md b/README.md index d4a30d7..2106ea5 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ # [CodeChecker](http://github.com/Ericsson/CodeChecker/) C++ Static Analysis action -GitHub Action to execute static analysis over C-family projects (C, C++, -Objective-C) using the [Clang](http://clang.llvm.org/) infrastructure and -[CodeChecker](http://github.com/Ericsson/CodeChecker/) as its driver. +GitHub Action to execute static analysis over using [CodeChecker](http://github.com/Ericsson/CodeChecker/) as its driver. +For C-family projects (C, C++, Objective-C, CUDA, etc.), CodeChecker supports driving the static analysis programs of [Clang](http://clang.llvm.org). +Several other static analysers' output can be integrated into CodeChecker through the [report converter](http://codechecker.readthedocs.io/en/latest/tools/report-converter/). -## Overview +## Overview (for C-family projects) ⚠️ **CAUTION! This action has been written with commands that target Ubuntu-based distributions!** @@ -238,6 +238,60 @@ runs: exit 1 ``` +## Overview (for other analyses through the _report-converter_) + +⚠️ **CAUTION! This action has been written with commands that target Ubuntu-based distributions!** + +This single action composite script encompasses the following steps: + + 1. Obtain a package of CodeChecker. + 3. Use the `report-converter` to convert other analysers' reports to CodeChecker's format. + 4. Show the analysis results in the CI log, and create HTML reports that can be uploaded as an artefact. (Uploading is to be done by the user!) + 5. _(Optional)_ Check for the current commit introducing new bug reports against a known state. (Good for pull requests!) + 6. _(Optional)_ Upload the results to a running _CodeChecker server_. (Good for the main project!) + + +ℹ️ **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. + +Please refer to the documentation of the analyser of your choice for this. +CodeChecker does **NOT** support driving the analysis through external tools, but if a successful analysis had been done, it can convert and store the results. + +```yaml +job: + steps: + # Check YOUR project out! + - name: "Check out repository" + uses: actions/checkout@v2 + + # Perform the analysis. Details vary between analysers! + # Example for "PyLint" added below! + - name: "Analyse with PyLint" + run: | + sudo apt-get -y install pylint + pylint -f json --exit-zero myproject > pylint_reports.json + + # Run the conversion + - uses: whisperity/codechecker-analysis-action@v1 + id: codechecker + with: + report-converter: true + original-analyser: "pylint" + original-analysis-output: "pylint_reports.json" + + # Upload the results (after conversion by CodeChecker) to the CI. + - uses: actions/upload-artifact@v2 + with: + name: "CodeChecker Bug Reports" + path: ${{ steps.codechecker.outputs.result-html-dir }} +``` + +### Uploading results and acting as a CI gate + +The _report-converter_ tool converts the output of various analysers to the common format used by CodeChecker. +Once the conversion is done, the rest of the action's features can execute in the same fashion as for C/C++ projects. +Please refer to earlier parts of the documentation for the configuration of these features. + ## Action configuration | Variable | Default | Description | @@ -276,6 +330,16 @@ runs: 🔖 Read more about [`CodeChecker parse`](http://codechecker.readthedocs.io/en/latest/analyzer/user_guide/#parse) in the official documentation. +### Report conversion configuration + +🔖 Read more about the [`report-converter`](http://codechecker.readthedocs.io/en/latest/tools/report-converter/) in the official documentation. + +| Variable | Default | Description | +|----------------------------|---------|----------------------------------------------------------------------------------------------------------------------------------------------------| +| `report-converter` | `false` | If set to `true`, the job will execute _report conversion_ from other analysers instead of driving the static analysis by itself. | +| `original-analyser` | | The "type" of the analysis that **had been performed** earlier. Passed as mandatory input to the `report-converter` executable. | +| `original-analysis-output` | | The file or directory where the results of the third-party analyser are available. Passed as mandatory input to the `report-converter` executable. | + ### Diff configuration 🔖 Read more about [`CodeChecker cmd diff`](http://codechecker.readthedocs.io/en/latest/analyzer/web_guide/#cmd-diff) in the official documentation. @@ -310,7 +374,7 @@ The action exposes the following outputs which may be used in a workflow's steps | Variable | Value | Description | |--------------------|-------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------| -| `analyze-output` | Auto-generated, or `analyze-output` input | The directory where the **raw** analysis output files are available. | +| `analyze-output` | Auto-generated, or `analyze-output` input | The directory where the **raw** analysis output files (either created by the analysers, or by the converter) are available. | | `logfile` | Auto-generated, or `logfile` input | The JSON Compilation Database of the analysis that was executed. | | `diff-html-dir` | Auto-generated. | The directory where the **user-friendly HTML** bug reports were generated to about the **new** findings (if `diff` was enabled). | | `diff-result-log` | Auto-generated. | `CodeChecker cmd diff`'s output log file which contains the **new** findings dumped into it. | diff --git a/action.yml b/action.yml index c35cfd0..498fc53 100644 --- a/action.yml +++ b/action.yml @@ -47,6 +47,17 @@ inputs: required: true default: 'true' + report-converter: + description: 'Whether to perform report conversion from analyses executed by third-party analysers instead of driving the analysis via CodeChecker directly.' + required: true + default: 'false' + original-analyser: + description: 'The type of the third-party analyser which performed the analysis. Passed to the report converter executable, as a mandatory input parameter.' + required: false + original-analysis-output: + description: 'The location of the analysis data emitted by the third-party analyser. Passed to the report converter executable, as a mandatory input parameter.' + required: false + diff: description: 'Whether to enable calculating the different of the current analysis results against a run stored on a CodeChecker server. If enabled, other flags, such as "diff-url" must also be set.' required: true @@ -89,8 +100,8 @@ outputs: value: ${{ steps.log.outputs.COMPILATION_DATABASE }} analyze-output: - description: 'The output directory where the raw analysis output was stored to.' - value: ${{ steps.analyze.outputs.OUTPUT_DIR }} + description: 'The output directory where the raw analysis or converted output was stored to.' + value: ${{ steps.analyze-or-report.outputs.OUTPUT_DIR }} warnings: description: 'Whether the static analyser(s) reported any findings.' @@ -135,7 +146,7 @@ runs: - name: "Install LLVM (${{ inputs.llvm-version }})" id: llvm - if: ${{ inputs.llvm-version != 'ignore' }} + if: ${{ inputs.llvm-version != 'ignore' && inputs.report-converter != 'true' }} env: IN_LLVM_VERSION: ${{ inputs.llvm-version }} shell: bash @@ -159,6 +170,7 @@ runs: - name: "Prepare JSON Compilation Database" id: log + if: ${{ inputs.report-converter != 'true' }} env: ACTION_NAME: ${{ github.action }} CODECHECKER_PATH: ${{ steps.codechecker.outputs.PATH }} @@ -170,8 +182,9 @@ runs: shell: bash run: ${{ github.action_path }}/src/get-or-create-build-json.sh - - name: "Execute static analysis" + - name: "Execute static analysis for C/C++" id: analyze + if: ${{ inputs.report-converter != 'true' }} env: CODECHECKER_PATH: ${{ steps.codechecker.outputs.PATH }} COMPILATION_DATABASE: ${{ steps.log.outputs.COMPILATION_DATABASE }} @@ -179,18 +192,48 @@ runs: IN_CONFIGFILE: ${{ inputs.config }} IN_CTU: ${{ inputs.ctu }} - IN_FLAGS: ${{ inputs.analyze-options }} IN_IGNORE_CRASHES: ${{ inputs.ignore-analyze-crashes }} IN_OUTPUT_DIR: ${{ inputs.analyze-output }} shell: bash run: ${{ github.action_path }}/src/execute-analysis.sh - - name: "Parse and convert results" + - name: "Perform report-converter" + id: report-convert + if: ${{ inputs.report-converter == 'true' }} + env: + CODECHECKER_PATH: ${{ steps.codechecker.outputs.PATH }} + GITHUB_ACTION_NAME: ${{ github.action }} + + IN_ORIGINAL_ANALYSER: ${{ inputs.original-analyser }} + IN_ORIGINAL_ANALYSIS_OUTPUT: ${{ inputs.original-analysis-output }} + IN_IGNORE_CRASHES: ${{ inputs.ignore-analyze-crashes }} + IN_OUTPUT_DIR: ${{ inputs.analyze-output }} + shell: bash + run: ${{ github.action_path }}/src/report-converter.sh + + # This step is needed because it is forbidden to reuse the 'id' of a step, + # even if the two steps taking the same 'id' are mutually exclusive. + - name: "(Internal: set output variables for steps after analyze/convert)" + id: analyze-or-report + shell: bash + run: | + if [[ ! -z "$CODECHECKER_ACTION_DEBUG" ]]; then + set -x + fi + + if [[ "${{ inputs.report-converter }}" != "true" ]] + then + echo "::set-output name=OUTPUT_DIR::${{ steps.analyze.outputs.OUTPUT_DIR }}" + else + echo "::set-output name=OUTPUT_DIR::${{ steps.report-convert.outputs.OUTPUT_DIR }}" + fi + + - name: "Parse and convert results to HTML" id: parse env: PROJECT_PATH: ${{ github.workspace }} CODECHECKER_PATH: ${{ steps.codechecker.outputs.PATH }} - RAW_RESULT_DIR: ${{ steps.analyze.outputs.OUTPUT_DIR }} + RAW_RESULT_DIR: ${{ steps.analyze-or-report.outputs.OUTPUT_DIR }} IN_CONFIGFILE: ${{ inputs.config }} shell: bash @@ -220,7 +263,7 @@ runs: PROJECT_PATH: ${{ github.workspace }} CODECHECKER_PATH: ${{ steps.codechecker.outputs.PATH }} CODECHECKER_DIFF_RUN_NAME: ${{ steps.diff-pre.outputs.RUN_NAME }} - RAW_RESULT_DIR: ${{ steps.analyze.outputs.OUTPUT_DIR }} + RAW_RESULT_DIR: ${{ steps.analyze-or-report.outputs.OUTPUT_DIR }} IN_CONFIGFILE: ${{ inputs.config }} IN_DIFF_URL: ${{ inputs.diff-url }} @@ -251,7 +294,7 @@ runs: CODECHECKER_PATH: ${{ steps.codechecker.outputs.PATH }} CODECHECKER_STORE_RUN_NAME: ${{ steps.store-pre.outputs.RUN_NAME }} CODECHECKER_STORE_RUN_TAG: ${{ steps.store-pre.outputs.RUN_TAG }} - RAW_RESULT_DIR: ${{ steps.analyze.outputs.OUTPUT_DIR }} + RAW_RESULT_DIR: ${{ steps.analyze-or-report.outputs.OUTPUT_DIR }} IN_CONFIGFILE: ${{ inputs.config }} IN_STORE_URL: ${{ inputs.store-url }} diff --git a/src/report-converter.sh b/src/report-converter.sh new file mode 100755 index 0000000..4cff3a2 --- /dev/null +++ b/src/report-converter.sh @@ -0,0 +1,48 @@ +#!/bin/bash +if [[ ! -z "$CODECHECKER_ACTION_DEBUG" ]]; then + set -x +fi + +echo "::group::Preparing for conversion" +if [[ -z "$IN_ORIGINAL_ANALYSER" ]]; then + echo "::error title=Internal error::environment variable 'IN_ORIGINAL_ANALYSER' missing!" + exit 1 +fi +if [[ -z "$IN_ORIGINAL_ANALYSIS_OUTPUT" ]]; then + echo "::error title=Internal error::environment variable 'IN_ORIGINAL_ANALYSIS_OUTPUT' missing!" + exit 1 +fi + +OUTPUT_DIR="$IN_OUTPUT_DIR" +if [[ -z "$OUTPUT_DIR" ]]; then + OUTPUT_DIR=~/"$GITHUB_ACTION_NAME"_Results +fi + +mkdir -pv "$(dirname $"OUTPUT_DIR")" + +# Report-Converter does not support a config file. :( +# if [[ ! -z "$IN_CONFIGFILE" ]]; then +# CONFIG_FLAG_1="--config" +# CONFIG_FLAG_2=$IN_CONFIGFILE +# echo "Using configuration file \"$IN_CONFIGFILE\"!" +# fi +echo "::endgroup::" + +echo "::group::Performing conversion" +"$CODECHECKER_PATH"/report-converter \ + "$IN_ORIGINAL_ANALYSIS_OUTPUT" \ + --type "$IN_ORIGINAL_ANALYSER" \ + --output "$OUTPUT_DIR" \ + --export plist +EXIT_CODE=$? +echo "::endgroup::" + +if [[ $EXIT_CODE -ne 0 && "$IN_IGNORE_CRASHES" == "true" ]]; then + # In general it is a good idea not to destroy the entire job just because a + # few translation units failed. Crashes are, unfortunately, usual. + echo "::warning title=Report Converter crashed on some inputs::Some of the analysis results failed to convert due to internal error." + EXIT_CODE=0 +fi + +echo "::set-output name=OUTPUT_DIR::$OUTPUT_DIR" +exit $EXIT_CODE diff --git a/test/report-converter/testpylint.py b/test/report-converter/testpylint.py new file mode 100644 index 0000000..ad42338 --- /dev/null +++ b/test/report-converter/testpylint.py @@ -0,0 +1,4 @@ +class ReturnInInitE0101: + def __init__(self, value): + # Should trigger "return-in-init" + return value