Repository navigation
fix(metrics): eleven ABC, cyclomatic and self-scan fixes #134
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Python CLI wheels | |
| # Build, smoke-test, and publish pip-installable wheels for the `bca` | |
| # command-line tool (distribution name `big-code-analysis-cli`, console | |
| # command `bca`). See #408. | |
| # | |
| # This is the binary-distribution sibling of `python-wheels.yml`, which | |
| # ships the PyO3 *library* bindings (`big-code-analysis`). The two are | |
| # independent PyPI projects that happen to share maturin as a build | |
| # backend: | |
| # | |
| # * `python-wheels.yml` → `import big_code_analysis` (extension wheel, | |
| # abi3, one wheel per arch covering every CPython 3.x). | |
| # * this workflow → `bca` on PATH (a `-b bin` wheel: the compiled | |
| # binary is packaged as a console script). | |
| # | |
| # Design notes specific to the bin wheel: | |
| # | |
| # * **Not an extension module — abi3 is irrelevant.** A bin wheel is | |
| # tagged `py3-none-<platform>`: one wheel per (OS, arch), but a single | |
| # wheel covers every CPython 3.x (and PyPy) on that platform. There is | |
| # no per-Python-version matrix and no libpython link. | |
| # | |
| # * **Bindings mode is fixed in `big-code-analysis-cli/pyproject.toml`** | |
| # (`[tool.maturin] bindings = "bin"`). The crate is already a single | |
| # `[[bin]] name = "bca"` with no pyo3/cdylib target, which is exactly | |
| # the shape maturin auto-detects for bin bindings; the explicit key | |
| # guards against a future PyO3 addition silently flipping the mode. | |
| # | |
| # * **Full grammar set is inherited from the crate.** The CLI crate | |
| # pins `big-code-analysis` with `default-features = false, features = | |
| # ["all-languages"]` (see #252), so a plain `-b bin` build already | |
| # compiles every grammar in — no `[tool.maturin] features` wiring is | |
| # needed here, and a default-features build cannot silently drop a | |
| # grammar. | |
| # | |
| # * **manylinux_2_28** floor, matching `python-wheels.yml`: the MSRV | |
| # (1.94) toolchain lives in the 2_28 container and RHEL/CentOS Stream | |
| # 8 (glibc 2.28) is the oldest realistic target. | |
| # | |
| # * **Compliance artefacts ride in the wheel.** The per-binary | |
| # `THIRD-PARTY-LICENSES-bca.md` (rendered by cargo-about, the same | |
| # tool `release.yml` uses for the deb/rpm/archive TPLs) plus the | |
| # workspace `LICENSE` are staged into the crate directory and picked | |
| # up by `[project].license-files`, landing in the wheel's standard | |
| # `.dist-info/licenses/`. The `bca` man pages are bundled for | |
| # reference via `[tool.maturin] include`. maturin additionally emits a | |
| # CycloneDX SBOM into `.dist-info/sboms/` automatically. | |
| # | |
| # * **PyPI Trusted Publishing** (OIDC), no long-lived token — identical | |
| # posture to `python-wheels.yml`. The deployment environment is | |
| # `pypi-cli`, intentionally distinct from the library's `pypi` | |
| # environment so the two projects' Trusted-Publisher OIDC claims | |
| # (matched on repo + workflow filename + environment) do not overlap. | |
| on: | |
| push: | |
| # Numeric-prefix glob (not bare `v*`) so a word-prefixed debugging tag | |
| # cannot reach the publish job — mirrors python-wheels.yml. | |
| tags: ['v[0-9]*'] | |
| pull_request: | |
| paths: | |
| - 'big-code-analysis-cli/**' | |
| - '.github/workflows/python-cli-wheels.yml' | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' && !startsWith(github.ref, 'refs/tags/') }} | |
| # Every `uses:` is pinned to a commit SHA (trailing comment = the tag it | |
| # points at); Dependabot bumps both in lockstep. SHAs are kept in sync | |
| # with python-wheels.yml / release.yml. | |
| env: | |
| CARGO_TERM_COLOR: always | |
| CARGO_INCREMENTAL: 0 | |
| CARGO_NET_RETRY: 10 | |
| RUSTUP_MAX_RETRIES: 10 | |
| PYTHON_VERSION_HOST: "3.12" | |
| # Pinned toolchain so the macOS / Windows wheel binaries (built on the | |
| # host, outside maturin's manylinux container) are reproducible from a | |
| # tag rather than tracking a floating `stable`. Matches release.yml's | |
| # RUST_TOOLCHAIN; the Linux legs build inside the 2_28 container with | |
| # its own toolchain, unaffected by this value. | |
| RUST_TOOLCHAIN: "1.94.0" | |
| # cargo-about renders the per-binary TPL; pinned to the version | |
| # release.yml uses so the deb/rpm/archive/wheel TPLs are byte-identical | |
| # for a given tag. | |
| CARGO_ABOUT_VERSION: "0.8.4" | |
| jobs: | |
| # Build the wheel matrix. PR-time builds are opt-in via the | |
| # `python-cli-wheels` label to keep the cost off Rust-only PRs that | |
| # merely brush a path-filter neighbour. Tag pushes and | |
| # workflow_dispatch always run. | |
| build: | |
| name: build (${{ matrix.target }}) | |
| if: >- | |
| github.event_name != 'pull_request' | |
| || contains(github.event.pull_request.labels.*.name, 'python-cli-wheels') | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-unknown-linux-gnu | |
| runs-on: ubuntu-latest | |
| manylinux: "2_28" | |
| wheel_tag: manylinux_2_28_x86_64 | |
| - target: aarch64-unknown-linux-gnu | |
| runs-on: ubuntu-24.04-arm | |
| manylinux: "2_28" | |
| wheel_tag: manylinux_2_28_aarch64 | |
| - target: x86_64-apple-darwin | |
| runs-on: macos-latest | |
| manylinux: "auto" | |
| # Glob (the macOS minor version varies) so the verify step | |
| # below distinguishes x86_64 from arm64 — a bare `macosx` | |
| # would accept a wrong-arch wheel from a cross-build slip. | |
| wheel_tag: 'macosx_*_x86_64' | |
| - target: aarch64-apple-darwin | |
| runs-on: macos-latest | |
| manylinux: "auto" | |
| wheel_tag: 'macosx_*_arm64' | |
| - target: x86_64-pc-windows-msvc | |
| runs-on: windows-latest | |
| manylinux: "auto" | |
| wheel_tag: win_amd64 | |
| runs-on: ${{ matrix.runs-on }} | |
| timeout-minutes: 45 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 1 | |
| submodules: false | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION_HOST }} | |
| # Host toolchain — needed for the cargo-about staging step below. | |
| # The Linux legs build the binary inside maturin-action's | |
| # manylinux_2_28 container (its own toolchain), but cargo-about runs | |
| # on the host; the `targets:` add lets cargo-about's | |
| # `--filter-platform` resolve a cross target (e.g. x86_64 on the | |
| # arm64 macOS runner) without a full target install. | |
| - uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772 # stable tip | |
| with: | |
| toolchain: ${{ env.RUST_TOOLCHAIN }} | |
| targets: ${{ matrix.target }} | |
| - name: Install cargo-about | |
| uses: taiki-e/install-action@fa23953489c080190314742a9b907f8e97c6767c # v2.87.10 | |
| with: | |
| tool: cargo-about@${{ env.CARGO_ABOUT_VERSION }} | |
| # Stage the compliance + doc artefacts into the CLI crate directory | |
| # so maturin picks them up (TPL + LICENSE via | |
| # `[project].license-files`; man pages via `[tool.maturin] include`). | |
| # These are .gitignore'd; the canonical sources live at the repo | |
| # root and `man/`. cargo-about is target-filtered so the TPL reflects | |
| # the dependency closure actually shipped in this wheel. | |
| - name: Stage license + man-page artefacts | |
| shell: bash | |
| env: | |
| TARGET: ${{ matrix.target }} | |
| run: | | |
| set -euo pipefail | |
| cargo about generate --locked \ | |
| --config about.toml \ | |
| --target "$TARGET" \ | |
| --manifest-path big-code-analysis-cli/Cargo.toml \ | |
| about.hbs \ | |
| > big-code-analysis-cli/THIRD-PARTY-LICENSES-bca.md | |
| test -s big-code-analysis-cli/THIRD-PARTY-LICENSES-bca.md | |
| cp LICENSE big-code-analysis-cli/LICENSE | |
| # Bundle only the `bca` pages — `bca-web.1` belongs to the web | |
| # server binary, which this wheel does not ship. | |
| mkdir -p big-code-analysis-cli/man | |
| for f in man/bca*.1; do | |
| [ "$f" = "man/bca-web.1" ] && continue | |
| cp "$f" big-code-analysis-cli/man/ | |
| done | |
| test -f big-code-analysis-cli/man/bca.1 | |
| test ! -f big-code-analysis-cli/man/bca-web.1 | |
| # maturin-action builds the bin wheel. On Linux it pulls the | |
| # manylinux_2_28 container matching ${{ matrix.target }}; on | |
| # macOS/Windows the `manylinux: auto` value is a no-op and the build | |
| # runs natively. `--strip` drops debug symbols from the binary; | |
| # `--locked` honours the workspace Cargo.lock byte-for-byte so the | |
| # wheel's transitive-dep versions are reproducible from the tag. | |
| # Bindings mode is set in pyproject (`bindings = "bin"`). | |
| - name: Build bin wheel (${{ matrix.target }}) | |
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 | |
| with: | |
| working-directory: big-code-analysis-cli | |
| target: ${{ matrix.target }} | |
| manylinux: ${{ matrix.manylinux }} | |
| args: --release --strip --locked --out dist | |
| # Catch a tag / interpreter / arch regression here instead of at | |
| # PyPI upload time. A bin wheel must be `py3-none-<platform>`: the | |
| # `py3-none` segment proves it was not built as a per-version | |
| # extension wheel (the most plausible regression if the pyproject | |
| # bindings key were lost), and `${{ matrix.wheel_tag }}` (which | |
| # carries the architecture, e.g. `macosx_*_x86_64`) proves the | |
| # platform AND arch match this leg — so a cross-build that silently | |
| # emitted the host arch fails here rather than only at the publish | |
| # job's inventory. | |
| - name: Verify wheel is py3-none / ${{ matrix.wheel_tag }} | |
| shell: bash | |
| working-directory: big-code-analysis-cli | |
| env: | |
| WHEEL_TAG: ${{ matrix.wheel_tag }} | |
| run: | | |
| set -euo pipefail | |
| ls -la dist/ | |
| shopt -s nullglob | |
| all=(dist/*.whl) | |
| if [[ ${#all[@]} -ne 1 ]]; then | |
| echo "::error::expected exactly one wheel in dist/, found ${#all[@]}" | |
| exit 1 | |
| fi | |
| name=$(basename "${all[0]}") | |
| # WHEEL_TAG is matrix-controlled and may contain a `*` glob | |
| # (the macOS minor version varies), so it is intentionally | |
| # unquoted here to act as a case pattern, not a literal. | |
| # shellcheck disable=SC2254 | |
| case "$name" in | |
| *-py3-none-*${WHEEL_TAG}*.whl) : ;; | |
| *) | |
| echo "::error::wheel '$name' is not py3-none-*${WHEEL_TAG}*" | |
| exit 1 | |
| ;; | |
| esac | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: cli-wheels-${{ matrix.target }} | |
| path: big-code-analysis-cli/dist/*.whl | |
| if-no-files-found: error | |
| # Source distribution. PyPI fallback for niche architectures and a | |
| # reproducibility anchor. A `pip install` from this sdist rebuilds the | |
| # binary, so it needs a Rust toolchain on the consumer side — expected | |
| # for a bin crate; the prebuilt wheels above cover the common platforms. | |
| sdist: | |
| name: sdist | |
| if: >- | |
| github.event_name != 'pull_request' | |
| || contains(github.event.pull_request.labels.*.name, 'python-cli-wheels') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 1 | |
| submodules: false | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION_HOST }} | |
| # No artefact staging: the TPL/LICENSE/man pages are `format = | |
| # "wheel"` / generated, so they are intentionally absent from the | |
| # sdist (a source snapshot). The workspace Cargo.lock travels in the | |
| # tarball, so a downstream rebuild resolves identical dep versions. | |
| - name: Build sdist | |
| uses: PyO3/maturin-action@e83996d129638aa358a18fbd1dfb82f0b0fb5d3b # v1.51.0 | |
| with: | |
| working-directory: big-code-analysis-cli | |
| command: sdist | |
| args: --out dist | |
| - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: cli-sdist | |
| path: big-code-analysis-cli/dist/*.tar.gz | |
| if-no-files-found: error | |
| # Install each wheel into a fresh runner Python environment on a runner | |
| # of the matching platform and exercise the binary end-to-end. The | |
| # acceptance criteria | |
| # (#408) require `bca --version` and a parse that proves the | |
| # `all-languages` grammar set shipped. The x86_64-apple-darwin wheel is | |
| # cross-built on the arm64 macOS runner and cannot be executed here | |
| # (GitHub's macOS pool is arm64); its structural integrity is covered by | |
| # the build job's verify step, mirroring release.yml's handling of the | |
| # non-executable aarch64-windows lane. | |
| smoke-test: | |
| name: smoke-test (${{ matrix.target }}) | |
| needs: build | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| include: | |
| - target: x86_64-unknown-linux-gnu | |
| runs-on: ubuntu-latest | |
| - target: aarch64-unknown-linux-gnu | |
| runs-on: ubuntu-24.04-arm | |
| - target: aarch64-apple-darwin | |
| runs-on: macos-latest | |
| - target: x86_64-pc-windows-msvc | |
| runs-on: windows-latest | |
| runs-on: ${{ matrix.runs-on }} | |
| timeout-minutes: 15 | |
| steps: | |
| # Sparse, submodule-free checkout of just the smoke harness so the | |
| # step below can run `scripts/smoke/cli_wheel_smoke.sh` against the | |
| # downloaded wheel (#995 — extracted from the former inline `run:` | |
| # block so the assertions are shellcheck-lintable and also run | |
| # per-PR via smoke-dryrun.yml). | |
| - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| sparse-checkout: scripts/smoke | |
| submodules: false | |
| - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0 | |
| with: | |
| python-version: ${{ env.PYTHON_VERSION_HOST }} | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: cli-wheels-${{ matrix.target }} | |
| path: dist | |
| - name: Install wheel + assert bca on PATH | |
| shell: bash | |
| env: | |
| PYTHONUNBUFFERED: "1" | |
| # On a tag push this is the release version (`vX.Y.Z`); empty | |
| # on PR / workflow_dispatch runs, where there is no tag to | |
| # compare against. | |
| EXPECTED_TAG: ${{ github.ref_type == 'tag' && github.ref_name || '' }} | |
| run: | | |
| set -euo pipefail | |
| ls -la dist/ | |
| # Install the just-built wheel by path; the build job's verify | |
| # step guarantees dist/ holds exactly one wheel, and --no-index | |
| # makes an accidental PyPI fallback (e.g. a wheel whose | |
| # platform tag does not match this runner) fail loudly rather | |
| # than silently installing a stale published version. --no-deps | |
| # is a no-op today (the bin wheel has zero Python runtime deps) | |
| # but keeps a future dep addition from being resolved unpinned | |
| # here (OpenSSF Scorecard Pinned-Dependencies). | |
| python -m pip install --no-index --no-deps dist/*.whl | |
| # The console script lands in the setup-python interpreter's | |
| # scripts dir, which is on PATH. The assertions (version | |
| # lockstep against EXPECTED_TAG, the all-languages grammar set, | |
| # and the #530 integer-metric serialization) live in the | |
| # checked-in smoke harness so they also run per-PR via | |
| # smoke-dryrun.yml (#995). Invoke via `bash` so the Windows | |
| # matrix leg (Git Bash, no executable bit) runs it too. | |
| bash scripts/smoke/cli_wheel_smoke.sh | |
| # Aggregate gate so branch protection can require a single check name. | |
| # Every dependency is label-gated at PR time, so on an unlabelled PR all | |
| # `needs` resolve to `skipped`; the predicate fails on `skipped` too | |
| # (not just failure/cancelled) so an unlabelled run cannot green-tick | |
| # the required check without having built anything — same wrinkle as | |
| # python-wheels.yml's `wheels` gate. | |
| cli-wheels: | |
| name: cli-wheels | |
| if: always() | |
| needs: | |
| - build | |
| - sdist | |
| - smoke-test | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| steps: | |
| - name: Fail if any dependency did not succeed | |
| # `skipped` only counts as a failure when the build legs were | |
| # *meant* to run — tag pushes, workflow_dispatch, or a labelled | |
| # PR. On an unlabelled PR the legs are intentionally skipped, so | |
| # treating that skip as a failure would red-X every CLI-touching | |
| # PR for no signal (the wheel matrix is opt-in via the | |
| # `python-cli-wheels` label). The skipped-is-green required-check | |
| # loophole this clause guards against still applies on the runs | |
| # that are supposed to build. | |
| if: >- | |
| contains(needs.*.result, 'failure') | |
| || contains(needs.*.result, 'cancelled') | |
| || ( | |
| contains(needs.*.result, 'skipped') | |
| && ( | |
| github.event_name != 'pull_request' | |
| || contains(github.event.pull_request.labels.*.name, 'python-cli-wheels') | |
| ) | |
| ) | |
| run: exit 1 | |
| # PyPI publish — only on a numeric `v*` tag push with NO pre-release | |
| # suffix. `!contains(github.ref, '-')` mirrors release.yml's prerelease | |
| # rule exactly (it classifies any tag matching `*-*` as a prerelease | |
| # and skips the crates.io publish), so a single tag cannot land a | |
| # prerelease on PyPI while skipping crates.io. A release tag | |
| # (`refs/tags/v1.2.0`) has no hyphen; any suffix (`-rc1`, `-beta2`, | |
| # `-pre1`, …) does. The Trusted Publisher for `big-code-analysis-cli` | |
| # must be registered on PyPI (repo + this workflow filename + the | |
| # `pypi-cli` environment) before the first tagged release. | |
| publish: | |
| name: publish to PyPI | |
| needs: [build, sdist, smoke-test] | |
| if: >- | |
| github.event_name == 'push' | |
| && startsWith(github.ref, 'refs/tags/v') | |
| && !contains(github.ref, '-') | |
| runs-on: ubuntu-latest | |
| environment: | |
| name: pypi-cli | |
| url: https://pypi.org/project/big-code-analysis-cli/ | |
| permissions: | |
| # A job-level `permissions:` block replaces the workflow default | |
| # rather than augmenting it; restate `contents: read` for any future | |
| # step that needs repo access. | |
| contents: read | |
| # Exchanged for a one-off PyPI upload credential — no long-lived | |
| # token. `attestations: write` powers the PEP 740 Sigstore | |
| # attestations the publish action generates by default. | |
| id-token: write | |
| attestations: write | |
| steps: | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| pattern: cli-wheels-* | |
| path: dist | |
| merge-multiple: true | |
| - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 | |
| with: | |
| name: cli-sdist | |
| path: dist | |
| - name: Inventory artefacts | |
| run: | | |
| set -euo pipefail | |
| ls -la dist/ | |
| # Refuse to publish a half-matrix release: assert every | |
| # platform wheel plus the sdist arrived. A download-artifact | |
| # silently dropping one leg would otherwise leave PyPI users on | |
| # that platform with no wheel. | |
| shopt -s nullglob | |
| require() { | |
| local label=$1; shift | |
| local matches=("$@") | |
| if [[ ${#matches[@]} -eq 0 ]]; then | |
| echo "::error::Missing expected artefact: ${label}" | |
| exit 1 | |
| fi | |
| } | |
| require "manylinux_2_28 x86_64 wheel" dist/*-py3-none-manylinux_2_28_x86_64.whl | |
| require "manylinux_2_28 aarch64 wheel" dist/*-py3-none-manylinux_2_28_aarch64.whl | |
| require "macOS x86_64 wheel" dist/*-py3-none-macosx_*_x86_64.whl | |
| require "macOS arm64 wheel" dist/*-py3-none-macosx_*_arm64.whl | |
| require "Windows x86_64 wheel" dist/*-py3-none-win_amd64.whl | |
| require "sdist tarball" dist/*.tar.gz | |
| - name: Publish to PyPI | |
| uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2 | |
| with: | |
| packages-dir: dist | |
| attestations: true |