From a17b55fbc9ccd7373dda5cd41f82fc28866c3479 Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Mon, 14 Sep 2026 16:45:56 -0600 Subject: [PATCH 1/2] task: coverity --- .github/workflows/coverity.yml | 152 +++++++++++++++++++++++++++++++++ README.md | 1 + 2 files changed, 153 insertions(+) create mode 100644 .github/workflows/coverity.yml diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml new file mode 100644 index 0000000..614027a --- /dev/null +++ b/.github/workflows/coverity.yml @@ -0,0 +1,152 @@ +name: Coverity Scan + +# Public static analysis via the free Coverity Scan service (scan.coverity.com, +# operated by Black Duck). Coverity is a compiled-language analyzer: it must wrap +# the real C build with `cov-build`, so this workflow installs oneMKL, compiles +# both extensions under capture, and uploads the result for analysis. +# +# One-time setup required before the first run: +# 1. Register IntelPython/mkl-service at https://scan.coverity.com/github +# (sign in with GitHub; the project name must match COVERITY_PROJECT below). +# 2. Add two repository secrets (Settings -> Secrets and variables -> Actions): +# COVERITY_SCAN_TOKEN - the project token from the Project Settings tab +# COVERITY_SCAN_EMAIL - a maintainer email for build notifications +# +# Free-tier quota for a project under 100K LOC (mkl-service is ~1.5K) is 28 +# builds/week, max 4/day, so this runs on a weekly schedule plus on demand +# rather than per-push. +# +# `workflow_dispatch` only becomes available once this file is on the default +# branch, and there is no push trigger, so the first scan will not happen at +# merge time -- dispatch it manually instead of waiting for the Monday cron. + +on: + schedule: + - cron: "0 1 * * 1" # Mondays 01:00 UTC; well under the free build quota + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: coverity-${{ github.ref }} + cancel-in-progress: true + +env: + COVERITY_PROJECT: IntelPython/mkl-service + ONEAPI_ROOT: /opt/intel/oneapi + +jobs: + coverity-scan: + # Forks lack the COVERITY_SCAN_* secrets; only run on the canonical repo. + if: github.repository == 'IntelPython/mkl-service' + runs-on: ubuntu-latest + # backstop timeout for whole job + timeout-minutes: 60 + + defaults: + run: + # The implicit default is `bash -e {0}`, which has no pipefail, so the + # `| tee` below would report tee's status and hide a cov-build failure. + shell: bash -eo pipefail {0} + + steps: + - name: Checkout repo + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Python + uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + with: + python-version: "3.12" + architecture: x64 + + - name: Add Intel repository + # Keyring + signed-by rather than the `apt-key add` used elsewhere in + # this repo: apt-key is deprecated and slated for removal. + run: | + wget -qO- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \ + | gpg --dearmor | sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null + echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" \ + | sudo tee /etc/apt/sources.list.d/oneAPI.list + sudo apt-get update + + - name: Install Intel oneMKL + # Only oneMKL is needed: both conda recipes pin `c_compiler: gcc`, so the + # shipped packages are gcc builds and that is what Coverity should see. + # icx is exercised separately by build-with-clang.yml, and pulling the + # DPC++ compiler in here would analyze a configuration we do not ship. + timeout-minutes: 25 + run: sudo apt-get install -y intel-oneapi-mkl-devel + + - name: Install build dependencies + # Cython is pinned here only (not in pyproject.toml) to keep the generated + # code stable between scans, so Coverity CIDs and their triage survive + run: pip install "setuptools>=77" "cython==3.3.0" "wheel>=0.45.1" + + - name: Download Coverity Build Tool + timeout-minutes: 15 + env: + COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} + run: | + curl --location --no-progress-meter --fail-with-body \ + --retry 5 --retry-connrefused --retry-delay 5 \ + --data-urlencode "token=${COVERITY_SCAN_TOKEN}" \ + --data-urlencode "project=${COVERITY_PROJECT}" \ + --output cov-analysis.tar.gz \ + https://scan.coverity.com/download/linux64 + # An invalid token/project is answered with a small HTML error page and + # HTTP 200, not the multi-hundred-MB tarball, so --fail-with-body does + # not catch it. Fail loudly with a clear hint instead. + if [ "$(stat -c '%s' cov-analysis.tar.gz)" -lt 1000000 ]; then + echo "::error::Coverity build tool download failed. Verify the COVERITY_SCAN_TOKEN secret and that the registered project name matches '${COVERITY_PROJECT}'." + head -c 512 cov-analysis.tar.gz || true + exit 1 + fi + mkdir -p cov-analysis + tar -xzf cov-analysis.tar.gz --strip 1 -C cov-analysis + echo "${PWD}/cov-analysis/bin" >> "$GITHUB_PATH" + + - name: Configure Coverity for GCC + run: cov-configure --gcc + + - name: Build under cov-build + # cov-build wraps the compiler and can wedge without producing output; + # cap it so a hang fails fast instead of idling until the job timeout. + # 2 translation units normally finish in well under a minute. + timeout-minutes: 20 + run: | + # shellcheck disable=SC1091 + source "${ONEAPI_ROOT}/setvars.sh" + # setup.py hard-requires MKLROOT and only raises a bare ValueError if + # it is unset, so surface what setvars.sh resolved it to. + echo "MKLROOT=${MKLROOT}" + # A non-editable `pip install .` builds in a fresh temporary tree and + # pip does not cache wheels built from a direct path, so both C + # translation units (_mklinitmodule.c and the Cython-generated + # _mkl_service.c) are genuinely recompiled under cov-build. A stale + # build/ would instead yield "No files were emitted", which the + # upload rejects. + cov-build --dir cov-int pip install . --no-build-isolation --no-deps 2>&1 | tee cov-build.log + if ! grep -qE "Emitted [1-9][0-9]* .*compilation unit" cov-build.log; then + echo "::error::Coverity captured 0 compilation units — the C build did not run under cov-build." + exit 1 + fi + + - name: Submit results to Coverity Scan + timeout-minutes: 15 + env: + COVERITY_SCAN_TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }} + COVERITY_SCAN_EMAIL: ${{ secrets.COVERITY_SCAN_EMAIL }} + run: | + tar -czf cov-int.tgz cov-int + curl --no-progress-meter --fail-with-body \ + --retry 5 --retry-connrefused --retry-delay 5 \ + --form token="${COVERITY_SCAN_TOKEN}" \ + --form email="${COVERITY_SCAN_EMAIL}" \ + --form file=@cov-int.tgz \ + --form version="${GITHUB_SHA}" \ + --form description="GitHub Actions ${GITHUB_REF_NAME} (run ${GITHUB_RUN_ID})" \ + --form project="${COVERITY_PROJECT}" \ + https://scan.coverity.com/builds diff --git a/README.md b/README.md index 3eb6e97..adc31a9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # `mkl-service` - Python package for run-time control of Intel® oneAPI Math Kernel Library (oneMKL). [![Conda package](https://github.com/IntelPython/mkl-service/actions/workflows/conda-package.yml/badge.svg)](https://github.com/IntelPython/mkl-service/actions/workflows/conda-package.yml) [![Build mkl-service with clang](https://github.com/IntelPython/mkl-service/actions/workflows/build-with-clang.yml/badge.svg)](https://github.com/IntelPython/mkl-service/actions/workflows/build-with-clang.yml) +[![Coverity Scan Build Status](https://scan.coverity.com/projects/intelpython-mkl-service/badge.svg)](https://scan.coverity.com/projects/intelpython-mkl-service) [![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/IntelPython/mkl-service/badge)](https://securityscorecards.dev/viewer/?uri=github.com/IntelPython/mkl-service) From 9a0632334d8fddbfdf1bb19d30a584c656d598f2 Mon Sep 17 00:00:00 2001 From: "Harlow, Jordan" Date: Mon, 19 Oct 2026 12:04:05 -0600 Subject: [PATCH 2/2] Switch Coverity workflow to the Meson build - Install meson-python, ninja, cmake and mkl-devel from PyPI, as the other --no-build-isolation workflows do, instead of the oneAPI apt repo - Drop setvars.sh/MKLROOT; meson.build locates oneMKL through CMake - Refresh comments for the three extensions built by meson.build and document why COVERITY_PROJECT is the owner/repo name, not the URL slug - Pin actions/checkout and actions/setup-python to the SHAs master uses - List the workflow in .github/AGENTS.md --- .github/AGENTS.md | 2 + .github/workflows/coverity.yml | 77 ++++++++++++++++------------------ 2 files changed, 37 insertions(+), 42 deletions(-) diff --git a/.github/AGENTS.md b/.github/AGENTS.md index 985782a..52ec913 100644 --- a/.github/AGENTS.md +++ b/.github/AGENTS.md @@ -12,6 +12,7 @@ All build/test workflows cover Python 3.10-3.15 plus free-threaded 3.14t and 3.1 - **build_pip.yml** — validates editable build - **pre-commit.yml** — code quality checks (flake8, etc.) - **openssf-scorecard.yml** — security posture scanning +- **coverity.yml** — weekly Coverity Scan static analysis of the C/Cython extensions (scan.coverity.com) ## CI/CD policy - Keep build matrix (Python versions, platforms) in workflow files only @@ -24,6 +25,7 @@ All build/test workflows cover Python 3.10-3.15 plus free-threaded 3.14t and 3.1 ## Security - OpenSSF Scorecard runs automatically +- Coverity Scan runs weekly via `coverity.yml` (project `IntelPython/mkl-service` on scan.coverity.com; needs the `COVERITY_SCAN_TOKEN`/`COVERITY_SCAN_EMAIL` secrets) - CODEOWNERS enforces review policy - Dependabot monitors dependencies (`.github/dependabot.yml`) diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml index 614027a..ddf8fd3 100644 --- a/.github/workflows/coverity.yml +++ b/.github/workflows/coverity.yml @@ -2,19 +2,21 @@ name: Coverity Scan # Public static analysis via the free Coverity Scan service (scan.coverity.com, # operated by Black Duck). Coverity is a compiled-language analyzer: it must wrap -# the real C build with `cov-build`, so this workflow installs oneMKL, compiles -# both extensions under capture, and uploads the result for analysis. +# the real C build with `cov-build`, so this workflow installs the Meson build +# tooling and oneMKL, compiles all three extensions under capture, and uploads +# the result for analysis. # -# One-time setup required before the first run: -# 1. Register IntelPython/mkl-service at https://scan.coverity.com/github -# (sign in with GitHub; the project name must match COVERITY_PROJECT below). +# One-time setup, already done for IntelPython/mkl-service (repeat only if the +# project is ever re-registered): +# 1. Register the repo at https://scan.coverity.com/github (sign in with +# GitHub; the registered project name must match COVERITY_PROJECT below). # 2. Add two repository secrets (Settings -> Secrets and variables -> Actions): # COVERITY_SCAN_TOKEN - the project token from the Project Settings tab # COVERITY_SCAN_EMAIL - a maintainer email for build notifications # -# Free-tier quota for a project under 100K LOC (mkl-service is ~1.5K) is 28 -# builds/week, max 4/day, so this runs on a weekly schedule plus on demand -# rather than per-push. +# Free-tier quota for a project under 100K LOC (mkl-service is a few thousand) +# is 28 builds/week, max 4/day, so this runs on a weekly schedule plus on +# demand rather than per-push. # # `workflow_dispatch` only becomes available once this file is on the default # branch, and there is no push trigger, so the first scan will not happen at @@ -33,8 +35,12 @@ concurrency: cancel-in-progress: true env: + # Coverity Scan identifies a project by its registered name, which for + # projects registered through GitHub is the `owner/repo` name shown in the + # project page heading. The `intelpython-mkl-service` seen in + # scan.coverity.com URLs is the slug derived from that name, not the name + # itself. mkl_fft's workflow follows the same convention. COVERITY_PROJECT: IntelPython/mkl-service - ONEAPI_ROOT: /opt/intel/oneapi jobs: coverity-scan: @@ -52,38 +58,29 @@ jobs: steps: - name: Checkout repo - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false - name: Setup Python - uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0 + uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 with: python-version: "3.12" architecture: x64 - - name: Add Intel repository - # Keyring + signed-by rather than the `apt-key add` used elsewhere in - # this repo: apt-key is deprecated and slated for removal. - run: | - wget -qO- https://apt.repos.intel.com/intel-gpg-keys/GPG-PUB-KEY-INTEL-SW-PRODUCTS.PUB \ - | gpg --dearmor | sudo tee /usr/share/keyrings/oneapi-archive-keyring.gpg > /dev/null - echo "deb [signed-by=/usr/share/keyrings/oneapi-archive-keyring.gpg] https://apt.repos.intel.com/oneapi all main" \ - | sudo tee /etc/apt/sources.list.d/oneAPI.list - sudo apt-get update - - - name: Install Intel oneMKL - # Only oneMKL is needed: both conda recipes pin `c_compiler: gcc`, so the - # shipped packages are gcc builds and that is what Coverity should see. - # icx is exercised separately by build-with-clang.yml, and pulling the - # DPC++ compiler in here would analyze a configuration we do not ship. - timeout-minutes: 25 - run: sudo apt-get install -y intel-oneapi-mkl-devel - - name: Install build dependencies + # The build below runs with --no-build-isolation, so pyproject.toml's + # build-system.requires (meson-python, ninja, cmake, mkl-devel) must be + # installed up front, as build-with-standard-clang.yml does. The + # mkl-devel wheel ships the oneMKL headers, libraries and CMake config + # that meson.build locates via `dependency('MKL', method: 'cmake')`: + # setup-python puts the interpreter prefix's bin/ on PATH, and CMake's + # find_package searches PATH entries with bin/ stripped as install + # prefixes, so no MKLROOT or setvars.sh is involved. If a scan ever + # fails at configure time, that lookup is the first thing to check. # Cython is pinned here only (not in pyproject.toml) to keep the generated # code stable between scans, so Coverity CIDs and their triage survive - run: pip install "setuptools>=77" "cython==3.3.0" "wheel>=0.45.1" + run: pip install meson-python ninja cmake "cython==3.3.0" mkl-devel - name: Download Coverity Build Tool timeout-minutes: 15 @@ -114,20 +111,16 @@ jobs: - name: Build under cov-build # cov-build wraps the compiler and can wedge without producing output; # cap it so a hang fails fast instead of idling until the job timeout. - # 2 translation units normally finish in well under a minute. + # 3 translation units normally finish in well under a minute. timeout-minutes: 20 run: | - # shellcheck disable=SC1091 - source "${ONEAPI_ROOT}/setvars.sh" - # setup.py hard-requires MKLROOT and only raises a bare ValueError if - # it is unset, so surface what setvars.sh resolved it to. - echo "MKLROOT=${MKLROOT}" - # A non-editable `pip install .` builds in a fresh temporary tree and - # pip does not cache wheels built from a direct path, so both C - # translation units (_mklinitmodule.c and the Cython-generated - # _mkl_service.c) are genuinely recompiled under cov-build. A stale - # build/ would instead yield "No files were emitted", which the - # upload rejects. + # A non-editable `pip install .` has meson-python configure and compile + # in a fresh temporary build directory, and pip does not cache wheels + # built from a local directory, so all three C translation units + # (_mklinitmodule.c and the Cython-generated _py_mkl_service.c and + # _mkl_memory.c) are genuinely recompiled under cov-build. An editable + # install reusing a stale build/ would instead yield "No files were + # emitted", which the upload rejects. cov-build --dir cov-int pip install . --no-build-isolation --no-deps 2>&1 | tee cov-build.log if ! grep -qE "Emitted [1-9][0-9]* .*compilation unit" cov-build.log; then echo "::error::Coverity captured 0 compilation units — the C build did not run under cov-build."