diff --git a/.actrc b/.actrc new file mode 100644 index 0000000..f599d5d --- /dev/null +++ b/.actrc @@ -0,0 +1,2 @@ +--container-architecture linux/amd64 +-W .github/workflows/native_full_build.yml diff --git a/.github/workflows/coverity_component_full_scan.yml b/.github/workflows/coverity_component_full_scan.yml new file mode 100644 index 0000000..05fbd96 --- /dev/null +++ b/.github/workflows/coverity_component_full_scan.yml @@ -0,0 +1,87 @@ +name: Coverity Full Analysis Scan + +# Reusable workflow — called by coverity_full_scan.yml. +# Runs a full cov-build + cov-analyze + cov-commit-defects cycle. +# Results are committed to the Coverity Connect server stream only; +# nothing is posted back to any pull request. +on: + workflow_call: + inputs: + buildCommand: + description: 'Build Command' + required: true + type: string + branchName: + description: 'Branch Name' + required: true + type: string + customSetup: + description: 'Custom setup commands' + required: false + type: string + secrets: + COVERITY_APIKEY: + required: true + ARTIFACTORY_USER_APIKEY: + required: true + # GITHUB_TOKENCM: cross-org token — required if customSetup clones private repos + GITHUB_TOKENCM: + required: false + +permissions: + contents: read + +jobs: + coverity_full_scan: + runs-on: comcast-ubuntu-latest + container: + # TODO (org admin): provision vars.DOCKER_REGISTRY in rdkcentral org + image: ${{ vars.DOCKER_REGISTRY }}/rdk-docker/docker-rdk-coverity:1.0.7 + credentials: + # TODO (org admin): provision vars.ARTIFACTORY_USER in rdkcentral org + username: ${{ vars.ARTIFACTORY_USER }} + password: ${{ secrets.ARTIFACTORY_USER_APIKEY }} + + env: + # TODO (org admin): provision vars.COVERITY_URL and vars.COVERITY_USER in rdkcentral org + COVERITY_URL: ${{ vars.COVERITY_URL }} + COVERITY_USER: ${{ vars.COVERITY_USER }} + COVERITY_APIKEY: ${{ secrets.COVERITY_APIKEY }} + COVERITY_PROJECT_NAME: ${{ github.event.repository.name }} + COVERITY_STREAM_NAME: ${{ github.event.repository.name }}_${{ inputs.branchName }} + BUILD_COMMAND: ${{ inputs.buildCommand }} + GITHUB_TOKENCM: ${{ secrets.GITHUB_TOKENCM }} + COVERITY_UNSUPPORTED_COMPILER_INVOCATION: 1 + + steps: + - uses: actions/checkout@v4 + + - name: Custom setup + if: ${{ inputs.customSetup }} + run: eval "${{ inputs.customSetup }}" + + - name: Coverity Full Analysis Scan + run: | + export PATH=$PATH:/opt/coverity/bin + set -x + cd $GITHUB_WORKSPACE + mkdir -p config + cov-configure --gcc + cov-build --dir coverity_dir $BUILD_COMMAND + cov-analyze --dir coverity_dir --one-tu-per-psf false --disable-spotbugs --aggressiveness-level low --enable DC.STRING_BUFFER --all + + max_retries=3 + retries=0 + retry_timeout_sec=30 + success=false + while [ $retries -lt $max_retries ]; do + echo "Attempt $((retries + 1)) of $max_retries for cov-commit-defects" + if cov-commit-defects --dir coverity_dir --stream $COVERITY_STREAM_NAME \ + --url $COVERITY_URL --user $COVERITY_USER --password $COVERITY_APIKEY; then + success=true + break + fi + retries=$((retries + 1)) + sleep $((retries * retry_timeout_sec)) + done + $success || { echo "cov-commit-defects failed after $max_retries attempts"; exit 1; } diff --git a/.github/workflows/coverity_component_incremental_scan.yml b/.github/workflows/coverity_component_incremental_scan.yml new file mode 100644 index 0000000..ed68749 --- /dev/null +++ b/.github/workflows/coverity_component_incremental_scan.yml @@ -0,0 +1,136 @@ +name: Coverity Incremental Analysis Scan + +# Reusable workflow — called by coverity_incremental_scan.yml. +# Runs cov-run-desktop against changed files only and posts findings +# as pull request comments via synopsys-sig/coverity-report-output-v7-json. +on: + workflow_call: + inputs: + pullRequestNumber: + description: 'Pull Request Number' + required: true + type: string + buildCommand: + description: 'Build Command' + required: true + type: string + branchName: + description: 'Branch Name (target/base branch)' + required: true + type: string + customSetup: + description: 'Custom setup commands' + required: false + type: string + secrets: + COVERITY_APIKEY: + required: true + ARTIFACTORY_USER_APIKEY: + required: true + # GITHUB_TOKEN: used to post PR feedback comments + GITHUB_TOKEN: + required: true + # GITHUB_TOKENCM: cross-org token — required if customSetup clones private repos + GITHUB_TOKENCM: + required: false + +permissions: + contents: read + pull-requests: write + +jobs: + coverity_incremental_scan: + runs-on: comcast-ubuntu-latest + container: + # TODO (org admin): provision vars.DOCKER_REGISTRY in rdkcentral org + image: ${{ vars.DOCKER_REGISTRY }}/rdk-docker/docker-rdk-coverity:1.0.7 + credentials: + # TODO (org admin): provision vars.ARTIFACTORY_USER in rdkcentral org + username: ${{ vars.ARTIFACTORY_USER }} + password: ${{ secrets.ARTIFACTORY_USER_APIKEY }} + + env: + # TODO (org admin): provision vars.COVERITY_URL and vars.COVERITY_USER in rdkcentral org + COVERITY_URL: ${{ vars.COVERITY_URL }} + COVERITY_USER: ${{ vars.COVERITY_USER }} + COVERITY_APIKEY: ${{ secrets.COVERITY_APIKEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + COVERITY_PROJECT_NAME: ${{ github.event.repository.name }} + COVERITY_STREAM_NAME: ${{ github.event.repository.name }}_${{ inputs.branchName }} + BUILD_COMMAND: ${{ inputs.buildCommand }} + COVERITY_UNSUPPORTED_COMPILER_INVOCATION: 1 + + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ format('refs/pull/{0}/merge', inputs.pullRequestNumber) }} + + - name: Custom setup + if: ${{ inputs.customSetup }} + run: | + echo "customSetup: ${{ inputs.customSetup }}" + eval "${{ inputs.customSetup }}" + + - name: Get Pull Request Changeset + id: changeset + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const pull_number = Number(${{ toJSON(inputs.pullRequestNumber) }}); + if (!Number.isInteger(pull_number) || pull_number <= 0) { + core.setFailed('Invalid pullRequestNumber input.'); + return; + } + const files = await github.paginate(github.rest.pulls.listFiles, { + owner: context.repo.owner, + repo: context.repo.repo, + pull_number, + per_page: 100 + }); + const addedModified = files + .filter((file) => ['added', 'modified', 'renamed'].includes(file.status)) + .map((file) => file.filename) + .join('\n'); + core.setOutput('added_modified', addedModified); + + - name: Coverity Incremental Analysis Scan + id: incremental_scan + if: ${{ steps.changeset.outputs.added_modified != '' }} + env: + CHANGED_FILES_RAW: ${{ steps.changeset.outputs.added_modified }} + run: | + export PATH=$PATH:/opt/coverity/bin + set -x + cd $GITHUB_WORKSPACE + echo "Changed files: $CHANGED_FILES_RAW" + mapfile -t changed_files <<< "$CHANGED_FILES_RAW" + # Phase 1: capture build into the coverity_dir intermediate database + cov-run-desktop --dir coverity_dir \ + --url $COVERITY_URL --user $COVERITY_USER --password $COVERITY_APIKEY \ + --stream $COVERITY_STREAM_NAME \ + --build "$BUILD_COMMAND" + # Phase 2: analyze changed files only, compare against stream baseline + # --exit1-if-defects true: workflow fails on new defects (maintainers can bypass + # via GitHub branch protection "Allow specified actors to bypass required pull requests") + cov-run-desktop --dir coverity_dir \ + --url $COVERITY_URL --user $COVERITY_USER --password $COVERITY_APIKEY \ + --stream $COVERITY_STREAM_NAME \ + --present-in-reference false \ + --ignore-uncapturable-inputs true \ + --exit1-if-defects true \ + --json-output-v7 coverity_dir/coverity-results.json \ + --allow-suffix-match --set-new-defect-owner false \ + "${changed_files[@]}" + + # Post findings as PR comments — raw Coverity output, no custom formatting + - name: Coverity Pull Request Feedback + if: ${{ always() && hashFiles('coverity_dir/coverity-results.json') != '' }} + uses: synopsys-sig/coverity-report-output-v7-json@v0.1.1 + with: + json-file-path: coverity_dir/coverity-results.json + github-token: ${{ secrets.GITHUB_TOKEN }} + coverity-url: ${{ vars.COVERITY_URL }} + coverity-project-name: ${{ github.event.repository.name }} + coverity-username: ${{ vars.COVERITY_USER }} + coverity-password: ${{ secrets.COVERITY_APIKEY }} diff --git a/.github/workflows/coverity_full_scan.yml b/.github/workflows/coverity_full_scan.yml new file mode 100644 index 0000000..8bab735 --- /dev/null +++ b/.github/workflows/coverity_full_scan.yml @@ -0,0 +1,24 @@ +name: Coverity Full Scan + +# Triggers on merges to primary branches. +# Results committed to Coverity Connect server (maintainer-only access). +# Nothing is posted back to any pull request. +on: + push: + branches: [ main, develop, '+([0-9])\.+([0-9])\.x-maintenance' ] + paths: ['**/*.c', '**/*.cpp', '**/*.cc', '**/*.cxx', '**/*.h', '**/*.hpp'] + +permissions: + contents: read + +jobs: + call-coverity-full-scan: + uses: ./.github/workflows/coverity_component_full_scan.yml + with: + branchName: ${{ github.ref_name }} + buildCommand: sh cov_build.sh + customSetup: sh build_dependencies.sh + secrets: + COVERITY_APIKEY: ${{ secrets.COVERITY_APIKEY }} + ARTIFACTORY_USER_APIKEY: ${{ secrets.ARTIFACTORY_USER_APIKEY }} + GITHUB_TOKENCM: ${{ secrets.RDKCM_RDKE }} diff --git a/.github/workflows/coverity_incremental_scan.yml b/.github/workflows/coverity_incremental_scan.yml new file mode 100644 index 0000000..ce24c0b --- /dev/null +++ b/.github/workflows/coverity_incremental_scan.yml @@ -0,0 +1,34 @@ +name: Coverity Incremental Scan + +permissions: + contents: read + pull-requests: write + +# Triggers on pull requests targeting primary branches. +# Scans only changed compilable source files. +# Findings are posted as pull request comments. +# Merges are not blocked outright — maintainers can bypass via branch protection. +on: + pull_request: + branches: [ main, develop, '+([0-9])\.+([0-9])\.x-maintenance' ] + paths: ['**/*.c', '**/*.cpp', '**/*.cc', '**/*.cxx', '**/*.h', '**/*.hpp'] + workflow_dispatch: + inputs: + pullRequestNumber: + description: 'Pull Request Number' + required: true + type: string + +jobs: + call-coverity-incremental-scan: + uses: ./.github/workflows/coverity_component_incremental_scan.yml + with: + pullRequestNumber: ${{ github.event.inputs.pullRequestNumber || github.event.pull_request.number }} + branchName: ${{ github.event.pull_request.base.ref || github.ref_name }} + buildCommand: sh cov_build.sh + customSetup: sh build_dependencies.sh + secrets: + COVERITY_APIKEY: ${{ secrets.COVERITY_APIKEY }} + ARTIFACTORY_USER_APIKEY: ${{ secrets.ARTIFACTORY_USER_APIKEY }} + GITHUB_TOKEN: ${{ secrets.RDKCM_RDKE }} + GITHUB_TOKENCM: ${{ secrets.RDKCM_RDKE }} diff --git a/.github/workflows/native_full_build.yml b/.github/workflows/native_full_build.yml new file mode 100644 index 0000000..9f4314d --- /dev/null +++ b/.github/workflows/native_full_build.yml @@ -0,0 +1,35 @@ +name: Build Component in Native Environment + +on: + push: + branches: [ main, '+([0-9])\.+([0-9])\.x-maintenance' ] + paths: ['**/*.c', '**/*.cpp', '**/*.cc', '**/*.cxx', '**/*.h', '**/*.hpp', 'CMakeLists.txt', 'cmake/**', 'build_dependencies.sh', 'cov_build.sh'] + pull_request: + branches: [ main, '+([0-9])\.+([0-9])\.x-maintenance' ] + paths: ['**/*.c', '**/*.cpp', '**/*.cc', '**/*.cxx', '**/*.h', '**/*.hpp', 'CMakeLists.txt', 'cmake/**', 'build_dependencies.sh', 'cov_build.sh'] + +permissions: + contents: read + +defaults: + run: + shell: bash + +jobs: + native-build: + name: Build firebolt-cpp-transport in native environment + runs-on: ubuntu-latest + container: + image: ghcr.io/rdkcentral/docker-rdk-ci:latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install build dependencies + run: sh -x build_dependencies.sh + + - name: Build + run: sh -x cov_build.sh + env: + GITHUB_TOKEN: ${{ secrets.RDKCM_RDKE }} diff --git a/build_dependencies.sh b/build_dependencies.sh new file mode 100755 index 0000000..d050731 --- /dev/null +++ b/build_dependencies.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# build_dependencies.sh — install all build dependencies for firebolt-cpp-transport +# +# Installs to the system prefix (/usr/local) and is intentionally idempotent: +# running it multiple times is safe. Mirrors the dep set and versions pinned in +# .github/Dockerfile so that both the native CI image and the Coverity container +# end up with an identical build environment. +# +# Usage: sh build_dependencies.sh +# (run as root, or with sudo, from any directory) +set -x +set -e + +DEPS_GOOGLETEST_V="1.15.2" +DEPS_NLOHMANN_JSON_V="3.11.3" +DEPS_JSON_SCHEMA_VALIDATOR_V="2.3.0" +DEPS_WEBSOCKETPP_V="0.8.2" + +# --------------------------------------------------------------------------- +# 1. System packages +# --------------------------------------------------------------------------- +apt-get update +apt-get install -y --no-install-recommends --fix-missing \ + build-essential ca-certificates \ + cmake pkg-config clang-format \ + libboost-all-dev \ + curl wget git \ + python3-pip + +if python3 -m pip help install | grep -q -- '--break-system-packages'; then + python3 -m pip install --break-system-packages gcovr +else + python3 -m pip install gcovr +fi + +# --------------------------------------------------------------------------- +# 2. googletest +# --------------------------------------------------------------------------- +WORK_DIR="$(mktemp -d)" +trap 'rm -rf "$WORK_DIR"' EXIT + +dir="googletest-${DEPS_GOOGLETEST_V}" +curl -sL "https://github.com/google/googletest/releases/download/v${DEPS_GOOGLETEST_V}/${dir}.tar.gz" \ + | tar xzf - -C "$WORK_DIR" +cmake -B "$WORK_DIR/build/${dir}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=ON \ + "$WORK_DIR/${dir}" +cmake --build "$WORK_DIR/build/${dir}" --target install + +# --------------------------------------------------------------------------- +# 3. nlohmann/json +# --------------------------------------------------------------------------- +dir="nlohmann-json-${DEPS_NLOHMANN_JSON_V}" +git clone --depth 1 --branch "v${DEPS_NLOHMANN_JSON_V}" \ + "https://github.com/nlohmann/json" "$WORK_DIR/${dir}" +cmake -B "$WORK_DIR/build/${dir}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=ON \ + -DJSON_BuildTests=OFF \ + "$WORK_DIR/${dir}" +cmake --build "$WORK_DIR/build/${dir}" --target install + +# --------------------------------------------------------------------------- +# 4. json-schema-validator +# --------------------------------------------------------------------------- +dir="json-schema-validator-${DEPS_JSON_SCHEMA_VALIDATOR_V}" +curl -sL "https://github.com/pboettch/json-schema-validator/archive/refs/tags/${DEPS_JSON_SCHEMA_VALIDATOR_V}.tar.gz" \ + | tar xzf - -C "$WORK_DIR" +cmake -B "$WORK_DIR/build/${dir}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=ON \ + -DJSON_VALIDATOR_BUILD_TESTS=OFF \ + -DJSON_VALIDATOR_BUILD_EXAMPLES=OFF \ + "$WORK_DIR/${dir}" +cmake --build "$WORK_DIR/build/${dir}" --target install + +# --------------------------------------------------------------------------- +# 5. websocketpp (header-only, cmake install registers package config) +# --------------------------------------------------------------------------- +dir="websocketpp-${DEPS_WEBSOCKETPP_V}" +curl -sL "https://github.com/zaphoyd/websocketpp/archive/refs/tags/${DEPS_WEBSOCKETPP_V}.tar.gz" \ + | tar xzf - -C "$WORK_DIR" +cmake -B "$WORK_DIR/build/${dir}" \ + -DCMAKE_BUILD_TYPE=Release \ + -DBUILD_SHARED_LIBS=ON \ + -DBUILD_TESTS=OFF \ + -DBUILD_EXAMPLES=OFF \ + "$WORK_DIR/${dir}" +cmake --build "$WORK_DIR/build/${dir}" --target install diff --git a/cov_build.sh b/cov_build.sh new file mode 100755 index 0000000..7b18782 --- /dev/null +++ b/cov_build.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash +# cov_build.sh — configure and build firebolt-cpp-transport +# +# Run from the repo root after build_dependencies.sh has prepared the +# environment. Produces a Debug build with tests enabled so that +# Coverity can intercept the full compilation including test code. +# +# Usage: sh cov_build.sh +set -x +set -e + +GITHUB_WORKSPACE="${GITHUB_WORKSPACE:-${PWD}}" +cd "${GITHUB_WORKSPACE}" + +cmake -B build-dev -S . \ + -DCMAKE_BUILD_TYPE=Debug \ + -DENABLE_TESTS=ON + +cmake --build build-dev --parallel diff --git a/coverity_local.sh b/coverity_local.sh new file mode 100755 index 0000000..3c727cb --- /dev/null +++ b/coverity_local.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +# coverity_local.sh — run a Coverity static analysis scan on your local machine +# +# Runs cov-build + cov-analyze inside the same Docker image used by CI. +# Results are written to ./coverity_dir/ and an HTML report to ./coverity_html/. +# No Coverity Connect server connection is made — raw tool output only. +# +# Prerequisites: +# docker login -u -p +# docker pull /rdk-docker/docker-rdk-coverity:1.0.7 +# +# Usage: +# bash coverity_local.sh +# ./coverity_local.sh --image # override image +set -e + +IMAGE="${COVERITY_IMAGE:-}" +while [[ $# -gt 0 ]]; do + case $1 in + --image) IMAGE="$2"; shift 2;; + *) echo "Unknown option: $1" >&2; exit 1;; + esac +done + +if [[ -z "$IMAGE" ]]; then + if [[ -z "${DOCKER_REGISTRY:-}" ]]; then + echo "ERROR: set DOCKER_REGISTRY or pass --image " >&2 + exit 1 + fi + IMAGE="${DOCKER_REGISTRY}/rdk-docker/docker-rdk-coverity:1.0.7" +fi + +docker run --rm \ + -e HOST_UID="$(id -u)" \ + -e HOST_GID="$(id -g)" \ + -v "$(pwd):/workspace" \ + -w /workspace \ + "$IMAGE" \ + bash -c ' + set -ex + export PATH=$PATH:/opt/coverity/bin + + # Install build dependencies + sh build_dependencies.sh + + # Capture build + cov-configure --gcc + cov-build --dir coverity_dir sh cov_build.sh + + # Analyze — same checker set as CI full scan + cov-analyze --dir coverity_dir \ + --one-tu-per-psf false \ + --disable-spotbugs \ + --aggressiveness-level low \ + --enable DC.STRING_BUFFER \ + --all + + # Emit raw text summary to stdout + cov-format-errors --dir coverity_dir --emacs-style + + # Emit HTML report for browsing + mkdir -p coverity_html + cov-format-errors --dir coverity_dir --html-output coverity_html + + # Keep generated outputs writable by the invoking host user + chown -R "${HOST_UID}:${HOST_GID}" coverity_dir coverity_html || true + + echo "" + echo "HTML report: coverity_html/index.html" + echo "Raw database: coverity_dir/" + '