forked from mirrors/kingfisher
initial support for distribution via pypi wheels
This commit is contained in:
parent
54775f0f43
commit
3294b2baf7
9 changed files with 438 additions and 0 deletions
126
.github/workflows/pypi.yml
vendored
Normal file
126
.github/workflows/pypi.yml
vendored
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
name: pypi-wheels
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
tag:
|
||||
description: "Release tag to package (e.g., v1.2.3). Leave blank to use Cargo.toml."
|
||||
required: false
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build-wheels:
|
||||
name: Build PyPI wheels
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Determine version/tag
|
||||
id: version
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
if [[ "${GITHUB_EVENT_NAME}" == "release" ]]; then
|
||||
TAG="${{ github.event.release.tag_name }}"
|
||||
elif [[ "${GITHUB_EVENT_NAME}" == "workflow_dispatch" && -n "${{ github.event.inputs.tag }}" ]]; then
|
||||
TAG="${{ github.event.inputs.tag }}"
|
||||
else
|
||||
VERSION=$(grep -m1 '^version\s*=' Cargo.toml | cut -d '"' -f2)
|
||||
TAG="v${VERSION}"
|
||||
fi
|
||||
VERSION="${TAG#v}"
|
||||
echo "tag=${TAG}" >> "$GITHUB_OUTPUT"
|
||||
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Download release assets
|
||||
env:
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
mkdir -p release-assets
|
||||
gh release download "${{ steps.version.outputs.tag }}" \
|
||||
-p "kingfisher-*.tgz" \
|
||||
-p "kingfisher-*.zip" \
|
||||
-D release-assets
|
||||
|
||||
- name: Extract binaries
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
mkdir -p extracted
|
||||
for archive in release-assets/*; do
|
||||
name=$(basename "$archive")
|
||||
dir="extracted/${name%.*}"
|
||||
mkdir -p "$dir"
|
||||
case "$archive" in
|
||||
*.tgz)
|
||||
tar -xzf "$archive" -C "$dir"
|
||||
;;
|
||||
*.zip)
|
||||
unzip -q "$archive" -d "$dir"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown archive: $archive" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
mkdir -p extracted/bin
|
||||
for bin in $(find extracted -type f -name "kingfisher" -o -name "kingfisher.exe"); do
|
||||
chmod +x "$bin" || true
|
||||
done
|
||||
|
||||
- name: Install build tooling
|
||||
run: python -m pip install --upgrade build
|
||||
|
||||
- name: Build wheels
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
version="${{ steps.version.outputs.version }}"
|
||||
|
||||
linux_x64=$(find extracted -type f -name "kingfisher" | rg -m1 "linux-x64" -)
|
||||
linux_arm64=$(find extracted -type f -name "kingfisher" | rg -m1 "linux-arm64" -)
|
||||
mac_x64=$(find extracted -type f -name "kingfisher" | rg -m1 "darwin-x64" -)
|
||||
mac_arm64=$(find extracted -type f -name "kingfisher" | rg -m1 "darwin-arm64" -)
|
||||
win_x64=$(find extracted -type f -name "kingfisher.exe" | rg -m1 "windows-x64" -)
|
||||
|
||||
scripts/build-pypi-wheel.sh \
|
||||
--binary "$linux_x64" \
|
||||
--version "$version" \
|
||||
--plat-name musllinux_1_2_x86_64
|
||||
|
||||
scripts/build-pypi-wheel.sh \
|
||||
--binary "$linux_arm64" \
|
||||
--version "$version" \
|
||||
--plat-name musllinux_1_2_aarch64
|
||||
|
||||
scripts/build-pypi-wheel.sh \
|
||||
--binary "$mac_x64" \
|
||||
--version "$version" \
|
||||
--plat-name macosx_10_9_x86_64
|
||||
|
||||
scripts/build-pypi-wheel.sh \
|
||||
--binary "$mac_arm64" \
|
||||
--version "$version" \
|
||||
--plat-name macosx_11_0_arm64
|
||||
|
||||
scripts/build-pypi-wheel.sh \
|
||||
--binary "$win_x64" \
|
||||
--version "$version" \
|
||||
--plat-name win_amd64
|
||||
|
||||
scripts/build-pypi-wheel.sh \
|
||||
--binary "$win_x64" \
|
||||
--version "$version" \
|
||||
--plat-name win_arm64
|
||||
|
||||
- name: Publish to PyPI (Trusted Publishing)
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
with:
|
||||
packages-dir: dist-pypi
|
||||
|
|
@ -16,6 +16,7 @@ This guide covers all installation methods for Kingfisher, including pre-commit
|
|||
- [Using the pre-commit Framework](#using-the-pre-commit-framework)
|
||||
- [Using Husky (Node.js projects)](#using-husky-nodejs-projects)
|
||||
- [Compile from Source](#compile-from-source)
|
||||
- [PyPI Wheels](#pypi-wheels)
|
||||
- [Run Kingfisher in Docker](#run-kingfisher-in-docker)
|
||||
|
||||
## Pre-built Releases
|
||||
|
|
@ -377,3 +378,22 @@ docker run --rm \
|
|||
--format json \
|
||||
--output /out/findings.json
|
||||
```
|
||||
|
||||
## PyPI Wheels
|
||||
|
||||
If you want to run Kingfisher from PyPI, install the `kingfisher-bin` package
|
||||
and use the `kingfisher` command it exposes:
|
||||
|
||||
```bash
|
||||
pip install kingfisher-bin
|
||||
kingfisher --help
|
||||
```
|
||||
|
||||
Or run it without installation using `uvx`:
|
||||
|
||||
```bash
|
||||
uvx kingfisher-bin --help
|
||||
```
|
||||
|
||||
For maintainers who need to build and publish wheels, see
|
||||
[docs/PYPI.md](PYPI.md).
|
||||
|
|
|
|||
91
docs/PYPI.md
Normal file
91
docs/PYPI.md
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
# PyPI Wheel Distribution (Kingfisher CLI)
|
||||
|
||||
This document describes how to package the Kingfisher Rust binary into
|
||||
platform-specific Python wheels so users can install and run `kingfisher` via
|
||||
`pip` or `uv`.
|
||||
|
||||
## Overview
|
||||
|
||||
The Python package is a thin wrapper that bundles the compiled Kingfisher binary
|
||||
inside `kingfisher/bin/` and exposes a `kingfisher` console entry point that
|
||||
executes it.
|
||||
|
||||
Users can run it without installation via `uvx`:
|
||||
|
||||
```bash
|
||||
uvx kingfisher-bin --help
|
||||
```
|
||||
|
||||
## Build prerequisites
|
||||
|
||||
1. Build the Kingfisher binary for your target platform (see
|
||||
[INSTALLATION.md](INSTALLATION.md) for `make` targets).
|
||||
2. Install the Python build tooling:
|
||||
|
||||
```bash
|
||||
python -m pip install build
|
||||
```
|
||||
|
||||
## Build a wheel
|
||||
|
||||
Run the helper script from the repo root:
|
||||
|
||||
```bash
|
||||
scripts/build-pypi-wheel.sh \
|
||||
--binary ./path/to/kingfisher \
|
||||
--version 1.2.3 \
|
||||
--plat-name manylinux_2_17_x86_64
|
||||
```
|
||||
|
||||
For Windows, pass the `.exe` binary and a Windows platform tag:
|
||||
|
||||
```bash
|
||||
scripts/build-pypi-wheel.sh \
|
||||
--binary .\\path\\to\\kingfisher.exe \
|
||||
--version 1.2.3 \
|
||||
--plat-name win_amd64
|
||||
```
|
||||
|
||||
If you only build a Windows x64 binary, you can still ship a `win_arm64` wheel
|
||||
using the same executable (it runs under emulation on ARM64 Windows):
|
||||
|
||||
```bash
|
||||
scripts/build-pypi-wheel.sh \
|
||||
--binary .\\path\\to\\kingfisher.exe \
|
||||
--version 1.2.3 \
|
||||
--plat-name win_arm64
|
||||
```
|
||||
|
||||
The resulting wheel will be placed in `dist-pypi/` by default.
|
||||
|
||||
## Test locally
|
||||
|
||||
```bash
|
||||
python -m pip install dist-pypi/kingfisher_bin-*.whl
|
||||
kingfisher --help
|
||||
```
|
||||
|
||||
## Publish
|
||||
|
||||
Upload the wheels to PyPI using `twine` (or your preferred tool):
|
||||
|
||||
```bash
|
||||
python -m pip install twine
|
||||
python -m twine upload dist-pypi/*
|
||||
```
|
||||
|
||||
### GitHub Actions (recommended)
|
||||
|
||||
The repository includes a `pypi-wheels` workflow that:
|
||||
|
||||
1. Downloads the release binaries.
|
||||
2. Builds platform-tagged wheels.
|
||||
3. Publishes them to PyPI using Trusted Publishing (OIDC).
|
||||
|
||||
To use Trusted Publishing, create a PyPI project named `kingfisher-bin` and
|
||||
enable GitHub Actions as a trusted publisher for this repository and workflow.
|
||||
No API token is required once Trusted Publishing is configured.
|
||||
|
||||
If you do not use Trusted Publishing, generate a PyPI API token and provide it
|
||||
to `twine` (for example via `TWINE_USERNAME=__token__` and
|
||||
`TWINE_PASSWORD=<pypi-token>`).
|
||||
17
pypi/README.md
Normal file
17
pypi/README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
# Kingfisher (Python wheel)
|
||||
|
||||
This package ships the Kingfisher CLI as a platform-specific Python wheel.
|
||||
The `kingfisher` console script executes the bundled binary for your
|
||||
OS/architecture.
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
pip install kingfisher-bin
|
||||
kingfisher --help
|
||||
```
|
||||
|
||||
## Development
|
||||
|
||||
Use the helper script in `scripts/build-pypi-wheel.sh` from the repo root to
|
||||
build a wheel for a specific target after compiling the Rust binary.
|
||||
48
pypi/kingfisher/__init__.py
Normal file
48
pypi/kingfisher/__init__.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
"""Python wrapper for the bundled Kingfisher binary."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from ._version import __version__
|
||||
|
||||
|
||||
def _binary_name() -> str:
|
||||
return "kingfisher.exe" if sys.platform == "win32" else "kingfisher"
|
||||
|
||||
|
||||
def get_binary_path() -> str:
|
||||
"""Return the path to the bundled Kingfisher binary."""
|
||||
binary = Path(__file__).resolve().parent / "bin" / _binary_name()
|
||||
|
||||
if not binary.exists():
|
||||
raise FileNotFoundError(
|
||||
"Kingfisher binary not found. "
|
||||
"This wheel may not match your platform."
|
||||
)
|
||||
|
||||
if sys.platform != "win32":
|
||||
current_mode = binary.stat().st_mode
|
||||
if not (current_mode & stat.S_IXUSR):
|
||||
binary.chmod(
|
||||
current_mode
|
||||
| stat.S_IXUSR
|
||||
| stat.S_IXGRP
|
||||
| stat.S_IXOTH
|
||||
)
|
||||
|
||||
return os.fspath(binary)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Execute the bundled Kingfisher binary."""
|
||||
binary = get_binary_path()
|
||||
|
||||
if sys.platform == "win32":
|
||||
raise SystemExit(subprocess.call([binary, *sys.argv[1:]]))
|
||||
|
||||
os.execvp(binary, [binary, *sys.argv[1:]])
|
||||
4
pypi/kingfisher/__main__.py
Normal file
4
pypi/kingfisher/__main__.py
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
from . import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
1
pypi/kingfisher/_version.py
Normal file
1
pypi/kingfisher/_version.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
__version__ = "0.0.0"
|
||||
43
pypi/pyproject.toml
Normal file
43
pypi/pyproject.toml
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "kingfisher-bin"
|
||||
description = "Kingfisher secret scanning CLI (packaged binary)"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
license = { text = "Apache-2.0" }
|
||||
authors = [
|
||||
{ name = "MongoDB" }
|
||||
]
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
]
|
||||
dynamic = ["version"]
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://github.com/mongodb/kingfisher"
|
||||
Repository = "https://github.com/mongodb/kingfisher"
|
||||
|
||||
[project.scripts]
|
||||
kingfisher = "kingfisher:main"
|
||||
|
||||
[tool.hatch.version]
|
||||
path = "kingfisher/_version.py"
|
||||
|
||||
[tool.hatch.build]
|
||||
include = [
|
||||
"kingfisher/**/*.py",
|
||||
"kingfisher/bin/*",
|
||||
"README.md",
|
||||
]
|
||||
|
||||
[tool.hatch.build.targets.wheel]
|
||||
only-include = [
|
||||
"kingfisher",
|
||||
"kingfisher/bin",
|
||||
"README.md",
|
||||
]
|
||||
88
scripts/build-pypi-wheel.sh
Executable file
88
scripts/build-pypi-wheel.sh
Executable file
|
|
@ -0,0 +1,88 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
usage() {
|
||||
cat <<'USAGE'
|
||||
Usage:
|
||||
scripts/build-pypi-wheel.sh \
|
||||
--binary /path/to/kingfisher[.exe] \
|
||||
--version 1.2.3 \
|
||||
--plat-name manylinux_2_17_x86_64 \
|
||||
[--out-dir dist-pypi]
|
||||
|
||||
Notes:
|
||||
- Build the Rust binary for your target platform before running this script.
|
||||
- Requires: python -m build (pip install build)
|
||||
USAGE
|
||||
}
|
||||
|
||||
binary_path=""
|
||||
version=""
|
||||
plat_name=""
|
||||
out_dir="dist-pypi"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--binary)
|
||||
binary_path="$2"
|
||||
shift 2
|
||||
;;
|
||||
--version)
|
||||
version="$2"
|
||||
shift 2
|
||||
;;
|
||||
--plat-name)
|
||||
plat_name="$2"
|
||||
shift 2
|
||||
;;
|
||||
--out-dir)
|
||||
out_dir="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown argument: $1" >&2
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ -z "$binary_path" || -z "$version" || -z "$plat_name" ]]; then
|
||||
usage
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$binary_path" ]]; then
|
||||
echo "Binary not found: $binary_path" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
root_dir="$(git rev-parse --show-toplevel)"
|
||||
pkg_dir="$root_dir/pypi"
|
||||
bin_dir="$pkg_dir/kingfisher/bin"
|
||||
|
||||
mkdir -p "$bin_dir" "$out_dir"
|
||||
|
||||
binary_name="kingfisher"
|
||||
if [[ "$binary_path" == *.exe ]]; then
|
||||
binary_name="kingfisher.exe"
|
||||
fi
|
||||
|
||||
cp "$binary_path" "$bin_dir/$binary_name"
|
||||
chmod +x "$bin_dir/$binary_name" || true
|
||||
|
||||
cat > "$pkg_dir/kingfisher/_version.py" <<EOF
|
||||
__version__ = "$version"
|
||||
EOF
|
||||
|
||||
python -m build \
|
||||
--wheel \
|
||||
--outdir "$out_dir" \
|
||||
--config-setting "--plat-name=$plat_name" \
|
||||
"$pkg_dir"
|
||||
|
||||
echo "Built wheel(s) in $out_dir"
|
||||
Loading…
Add table
Add a link
Reference in a new issue