diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b4b04f6..ae29fb4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -10,8 +10,8 @@ on: jobs: # Fetch tests do not produce results, but test the functionality of the # "grab LLVM" and "grab and build CodeChecker" features. - fetch_ubuntu_2004_latestllvm: - name: "Fetch test: Ubuntu Linux 20.04 (latest LLVM)" + fetch_ubuntu-2004_latest-llvm: + name: "Fetch: Ubuntu Linux 20.04 (latest LLVM)" runs-on: ubuntu-20.04 steps: - uses: actions/checkout@v2 @@ -20,8 +20,8 @@ jobs: version: "master" llvm-version: "latest" logfile: "test/empty/compile_commands.json" - fetch_ubuntu_1804_12llvm: - name: "Fetch test: Ubuntu Linux 18.04 (LLVM 12.y)" + fetch_ubuntu-1804_12-llvm: + name: "Fetch: Ubuntu Linux 18.04 (LLVM 12.y)" runs-on: ubuntu-18.04 steps: - uses: actions/checkout@v2 @@ -32,22 +32,46 @@ jobs: logfile: "test/empty/compile_commands.json" # Test simple build and analysis configuration. - simple_analysis_test: + simple-analysis-tests: strategy: fail-fast: false matrix: logfile: ['', 'test/simple/compile_commands.json'] build-command: ['', 'cd test/simple; g++ -c main.cpp -o main.o'] + analyze-output: ['', 'my-output-dir'] - name: "Simple analysis: (${{ matrix.logfile && 'logfile' || 'no-logfile' }} ${{ matrix.build-command && 'build-command' || 'no-build-command' }})" + 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, this step is "expected failure". + # 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: ./ with: logfile: ${{ matrix.logfile }} build-command: ${{ matrix.build-command }} + analyze-output: ${{ matrix.analyze-output }} + + analysis-cfg: + name: "Analysis: Custom configuration" + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - run: test/fix_compile_json_paths.sh + - uses: ./ + with: + config: 'test/codechecker.verbose.json' + logfile: 'test/simple/compile_commands.json' + analysis-ctu: + name: "Analysis: CTU shortcut" + runs-on: ubuntu-20.04 + steps: + - uses: actions/checkout@v2 + - run: test/fix_compile_json_paths.sh + - uses: ./ + with: + logfile: 'test/ctu/compile_commands.json' + ctu: true diff --git a/README.md b/README.md index 798a32c..09aa226 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,8 @@ Objective-C) using the [Clang](http://clang.llvm.org/) infrastructure and 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. â„šī¸ **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. @@ -26,11 +28,11 @@ The two versions are mutually exclusive — you either can give a compilatio ### Project can generate a [JSON Compilation Database](http://clang.llvm.org/docs/JSONCompilationDatabase.html) and build cleanly (no generated code) Some projects are trivial enough in their build configuration that no additional steps need to be taken after executing `configure.sh`, `cmake`, or similar tools. -If you are able to generate a *compilation database* from your build system **without** running the build itself, you can save some time, and go to the analysis immediately. +If you are able to generate a _compilation database_ from your build system **without** running the build itself, you can save some time, and go to the analysis immediately. You can specify the generated compilation database in the `logfile` variable -~~~{.yml} +```yaml runs: steps: # Check YOUR project out! @@ -48,18 +50,18 @@ runs: - uses: whisperity/codechecker-analysis-action with: logfile: ${{ github.workspace }}/Build/compile_commands.json -~~~ +``` ### Projects that need to self-creating a *JSON Compilation Database* or require generated code -Other kinds of projects might rely heavily on *generated code*. +Other kinds of projects might rely heavily on _generated code_. When looking at the source code of these projects **without** a build having been executed beforehand, they do not compile — as such, analysis cannot be executed either. In this case, you will need to instruct CodeChecker to log a build (and spend time doing the build) just before analysis. You can specify the build to execute in the `build-command` variable. -~~~{.yml} +```yaml runs: steps: # Check YOUR project out! @@ -77,7 +79,7 @@ runs: - uses: whisperity/codechecker-analysis-action with: build-command: "cd ${{ github.workspace }}/Build; cmake --build ." -~~~ +``` ## Action configuration @@ -103,4 +105,22 @@ runs: | `logfile` | | The location of the JSON Compilation Database which describes how the project is built. This flag is used if the build system can pre-generate the file for us. | | `build-command` | | The build command to execute. CodeChecker is capable of executing and logging the build for itself. This flag is used if the build-system can not generate the information by itself, or the project relies on other generated code. | +### Analysis configuration +🔖 Read more about [`CodeChecker analyze`](http://codechecker.readthedocs.io/en/latest/analyzer/user_guide/#analyze) in the official documentation. + + +| Variable | Default | Description | +|------------------|------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `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. | + + +## 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. | diff --git a/action.yml b/action.yml index 6c76938..2cc9d04 100644 --- a/action.yml +++ b/action.yml @@ -30,6 +30,24 @@ inputs: description: 'The build command to execute and log for the creation of a JSON Compilation Database. Mutually exclusive with "logfile".' required: false + analyze-output: + description: 'The output directory where the raw analysis output should be stored. If left the default empty, the path will be generated automatically.' + default: '' + required: false + ctu: + description: 'Whether to enable Cross Translation Unit (CTU) analysis in the Clang Static Analyzer.' + default: 'false' + required: true + +outputs: + logfile: + description: 'The location of the JSON Compilation Database that was used for the analysis.' + 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 }} + runs: using: "composite" steps: @@ -59,7 +77,7 @@ runs: - name: "Build and Package CodeChecker" id: codechecker env: - CODECHECKER_WILL_USE_WEB_API: false # TODO: Add support for this later. + CODECHECKER_WILL_USE_WEB_API: "false" # TODO: Add support for this later. shell: bash run: | set -ex @@ -89,13 +107,30 @@ runs: echo "::set-output name=GITSEVEN::$(./build/CodeChecker/bin/CodeChecker analyzer-version | grep 'Git commit' | cut -d'|' -f 2 | cut -c 2-8)" popd + - name: "Prepare JSON Compilation Database" + id: log env: + ACTION_NAME: ${{ github.action }} CODECHECKER_PATH: ${{ steps.codechecker.outputs.PATH }} IN_LOGFILE: ${{ inputs.logfile }} IN_COMMAND: ${{ inputs.build-command }} - OUT_FILE: ${{ github.workspace }}/codechecker_compilation_database.json + OUT_FILE: ${{ github.workspace }}/${{ github.action }}_codechecker_compilation_database.json shell: bash run: ${{ github.action_path }}/src/get-or-create-build-json.sh + + - name: "Execute static analysis" + id: analyze + env: + ACTION_NAME: ${{ github.action }} + CODECHECKER_PATH: ${{ steps.codechecker.outputs.PATH }} + COMPILATION_DATABASE: ${{ steps.log.outputs.COMPILATION_DATABASE }} + + IN_CONFIGFILE: ${{ inputs.config }} + IN_CTU: ${{ inputs.ctu }} + IN_FLAGS: ${{ inputs.analyze-options }} + IN_OUTPUT_DIR: ${{ inputs.analyze-output }} + shell: bash + run: ${{ github.action_path }}/src/execute-analysis.sh diff --git a/src/execute-analysis.sh b/src/execute-analysis.sh new file mode 100755 index 0000000..312d3cd --- /dev/null +++ b/src/execute-analysis.sh @@ -0,0 +1,41 @@ +#!/bin/bash +set -ex + +if [[ -z "$COMPILATION_DATABASE" ]]; then + echo "::error title=Internal error::environment variable 'COMPILATION_DATABASE' missing!" + exit 1 +fi + +OUTPUT_DIR="$IN_OUTPUT_DIR" +if [[ -z "$OUTPUT_DIR" ]]; then + OUTPUT_DIR=~/"$ACTION_NAME"_Results +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 + +CTU_FLAG=$([[ "$IN_CTU" == "true" ]] && echo "--ctu --ctu-ast-mode load-from-pch" || echo "") +if [[ "$IN_CTU" == "true" ]]; then + CTU_FLAG="--ctu --ctu-ast-mode load-from-pch" + echo "::notice title=Cross Translation Unit analyis::CTU has been enabled, the analysis might take a long time!" +fi + +# Note: Ignoring the result of the analyze command in CTU mode, as we do not +# wish to break the build on a CTU failure. + +"$CODECHECKER_PATH"/CodeChecker analyze \ + "$COMPILATION_DATABASE" \ + --output "$OUTPUT_DIR" \ + --jobs $(nproc) \ + $CONFIG_FLAG_1 $CONFIG_FLAG_2 \ + $CTU_FLAGS \ + || [[ "$IN_CTU" == "true" ]] +EXIT_CODE=$? + +echo "::set-output name=OUTPUT_DIR::$OUTPUT_DIR" +exit $EXIT_CODE diff --git a/src/get-or-create-build-json.sh b/src/get-or-create-build-json.sh index f6413c3..7804a2f 100755 --- a/src/get-or-create-build-json.sh +++ b/src/get-or-create-build-json.sh @@ -6,19 +6,24 @@ if [[ ! -z "$IN_LOGFILE" && ! -z "$IN_COMMAND" ]]; then exit 1 fi +mkdir -pv $(dirname "$OUT_FILE") + +EXIT_CODE=0 + if [[ ! -z "$IN_LOGFILE" ]]; then # Pretty trivial. cp -v "$IN_LOGFILE" "$OUT_FILE" - exit $? -fi - -if [[ ! -z "$IN_COMMAND" ]]; then + EXIT_CODE=$? +elif [[ ! -z "$IN_COMMAND" ]]; then "$CODECHECKER_PATH"/CodeChecker log \ --build "$IN_COMMAND" \ --output "$OUT_FILE" - exit $? + EXIT_CODE=$? +else + echo "::error title=Configuration error::neither 'logfile' nor 'build-command' specified!" + echo "[]" > "$OUT_FILE" + exit 1 fi -echo "::error title=Configuration error::neither 'logfile' nor 'build-command' specified!" -echo "[]" > "$OUT_FILE" -exit 1 +echo "::set-output name=COMPILATION_DATABASE::$OUT_FILE" +exit $EXIT_CODE diff --git a/test/codechecker.verbose.json b/test/codechecker.verbose.json new file mode 100644 index 0000000..3ddf9b8 --- /dev/null +++ b/test/codechecker.verbose.json @@ -0,0 +1,5 @@ +{ + "analyze": [ + "--verbose=debug" + ] +} diff --git a/test/ctu/compile_commands.json b/test/ctu/compile_commands.json new file mode 100644 index 0000000..4bcc0b6 --- /dev/null +++ b/test/ctu/compile_commands.json @@ -0,0 +1,12 @@ +[ + { + "directory": "__DIRECTORY__", + "command": "g++ -c main.cpp -o main.o", + "file": "main.cpp" + }, + { + "directory": "__DIRECTORY__", + "command": "g++ -c lib.cpp -o lib.o", + "file": "lib.cpp" + } +] diff --git a/test/ctu/lib.cpp b/test/ctu/lib.cpp new file mode 100644 index 0000000..0dea71a --- /dev/null +++ b/test/ctu/lib.cpp @@ -0,0 +1,3 @@ +int zero() { + return 0; +} diff --git a/test/ctu/main.cpp b/test/ctu/main.cpp new file mode 100644 index 0000000..442d4db --- /dev/null +++ b/test/ctu/main.cpp @@ -0,0 +1,5 @@ +int zero(); + +int main(int argc, char** argv) { + return argc / zero(); +} diff --git a/test/fix_compile_json_paths.sh b/test/fix_compile_json_paths.sh new file mode 100755 index 0000000..e370bcb --- /dev/null +++ b/test/fix_compile_json_paths.sh @@ -0,0 +1,8 @@ +#!/bin/bash +set -ex + +for CCDB in $(find . -name "compile_commands.json"); do + pushd $(dirname "$CCDB") + sed -i "s,__DIRECTORY__,$(pwd),g" "compile_commands.json" + popd +done diff --git a/test/simple/compile_commands.json b/test/simple/compile_commands.json index c96fcb4..08cee39 100644 --- a/test/simple/compile_commands.json +++ b/test/simple/compile_commands.json @@ -1,6 +1,6 @@ [ { - "directory": ".", + "directory": "__DIRECTORY__", "command": "g++ -c main.cpp -o main.o", "file": "main.cpp" }