Distribute flowmark-rs on PyPI via maturin (bindings = bin)
- Dominant language
- Rust
- Stars
- 17
- Forks
- 2
- Avg merge
- 5h 10m
- Merged PRs (30d)
- 7
Description
## Goal
Publish flowmark-rs to PyPI as a pre-built binary so users can install and run it with:
```bash
uvx flowmark-rs # run on demand (like npx)
uv tool install flowmark-rs # persistent install
pip install flowmark-rs # classic pip
```
No Rust toolchain required for end users. Just `uv` or `pip`.
## Why this approach
The original flowmark is a Python package on PyPI. The Rust rewrite is dramatically faster. Distributing the Rust binary through the same PyPI ecosystem keeps the install experience identical — just a different package name.
This is the same pattern used by **ruff**, **uv**, and **maturin** itself: a pure Rust CLI binary, packaged as a Python wheel, published to PyPI.
## How it works
[Maturin](https://github.com/PyO3/maturin) builds platform-specific Python wheels that contain the compiled Rust binary. When installed, the binary is placed on the user's PATH (e.g., in a virtualenv's `bin/` directory). No Python code runs — it's just a distribution mechanism.
The key setting is `bindings = "bin"` in `pyproject.toml`, which tells maturin this is a standalone binary (not a Python extension module).
## Files to add
### 1. `pyproject.toml`
Add at the repo root, next to `Cargo.toml`:
```toml
[build-system]
requires = ["maturin>=1.9,<2.0"]
build-backend = "maturin"
[project]
name = "flowmark-rs"
description = "Fast markdown formatter, written in Rust"
requires-python = ">=3.8"
license = { text = "MIT" }
readme = "README.md"
dynamic = ["version"]
classifiers = [
"Development Status :: 4 - Beta",
"Environment :: Console",
"Programming Language :: Rust",
"Topic :: Text Processing :: Markup :: Markdown",
]
[project.urls]
Repository = "https://github.com/jlevy/flowmark-rs"
[tool.maturin]
bindings = "bin"
strip = true
```
Key settings:
- **`bindings = "bin"`** — CLI binary, not a Python extension
- **`strip = true`** — strip debug symbols to reduce binary size (ruff does this)
- **`dynamic = ["version"]`** — maturin reads the version from `Cargo.toml` automatically
### 2. `.github/workflows/release.yml`
Builds wheels for all major platforms and publishes to PyPI on tagged releases:
```yaml
name: Release
on:
push:
tags:
- "v*"
permissions:
contents: read
jobs:
# ── Build: Linux x86_64 ──────────────────────────────────────────
build-linux-x86_64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: PyO3/maturin-action@v1
with:
maturin-version: latest
command: build
args: --release --locked --out dist
target: x86_64-unknown-linux-gnu
manylinux: "2_17"
- uses: actions/upload-artifact@v4
with:
name: wheels-linux-x86_64
path: dist/*.whl
# ── Build: Linux aarch64 ─────────────────────────────────────────
build-linux-aarch64:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: PyO3/maturin-action@v1
with:
maturin-version: latest
command: build
args: --release --locked --out dist
target: aarch64-unknown-linux-gnu
manylinux: "2_17"
- uses: actions/upload-artifact@v4
with:
name: wheels-linux-aarch64
path: dist/*.whl
# ── Build: macOS x86_64 ──────────────────────────────────────────
build-macos-x86_64:
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- uses: PyO3/maturin-action@v1
with:
maturin-version: latest
command: build
args: --release --locked --out dist
target: x86_64-apple-darwin
- uses: actions/upload-artifact@v4
with:
name: wheels-macos-x86_64
path: dist/*.whl
# ── Build: macOS aarch64 (Apple Silicon) ──────────────────────────
build-macos-aarch64:
runs-on: macos-14
steps:
- uses: actions/checkout@v4
- uses: PyO3/maturin-action@v1
with:
maturin-version: latest
command: build
args: --release --locked --out dist
target: aarch64-apple-darwin
- uses: actions/upload-artifact@v4
with:
name: wheels-macos-aarch64
path: dist/*.whl
# ── Build: Windows x86_64 ────────────────────────────────────────
build-windows-x86_64:
runs-on: windows-latest
steps:
- uses: actions/checkout@v4
- uses: PyO3/maturin-action@v1
with:
maturin-version: latest
command: build
args: --release --locked --out dist
target: x86_64-pc-windows-msvc
- uses: actions/upload-artifact@v4
with:
name: wheels-windows-x86_64
path: dist/*.whl
# ── Build: source distribution ───────────────────────────────────
build-sdist:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: PyO3/maturin-action@v1
with:
maturin-version: latest
command: sdist
args: --out dist
- uses: actions/upload-artifact@v4
with:
name: wheels-sdist
path: dist/*.tar.gz
# ── Publish to PyPI ──────────────────────────────────────────────
publish:
needs:
- build-linux-x86_64
- build-linux-aarch64
- build-macos-x86_64
- build-macos-aarch64
- build-windows-x86_64
- build-sdist
runs-on: ubuntu-latest
environment: release
permissions:
id-token: write # Required for PyPI trusted publishing
steps:
- uses: astral-sh/setup-uv@v7
- uses: actions/download-artifact@v4
with:
pattern: wheels-*
merge-multiple: true
path: wheels/
- run: uv publish wheels/*
```
## Notes on the workflow
- **manylinux: "2_17"** — glibc 2.17 compatibility covers virtually all Linux distros still in use (CentOS 7+, Ubuntu 14.04+, Debian 8+)
- **PyPI trusted publishing** — uses OIDC (`id-token: write`) so no API token secret is needed. Configure in PyPI project settings under "Trusted Publishers"
- **`--locked`** — uses `Cargo.lock` for reproducible builds
- **macOS runners** — `macos-13` is x86_64, `macos-14` is aarch64 (Apple Silicon)
- **sdist** — source distribution allows users to build from source as a fallback
## Platform coverage
| Platform | Target | Runner |
| --- | --- | --- |
| Linux x86_64 | `x86_64-unknown-linux-gnu` | `ubuntu-latest` |
| Linux ARM64 | `aarch64-unknown-linux-gnu` | `ubuntu-latest` (cross-compiled) |
| macOS Intel | `x86_64-apple-darwin` | `macos-13` |
| macOS Apple Silicon | `aarch64-apple-darwin` | `macos-14` |
| Windows x86_64 | `x86_64-pc-windows-msvc` | `windows-latest` |
Additional targets (musl, armv7, Windows ARM) can be added later — see [ruff's build-binaries.yml](https://github.com/astral-sh/ruff/blob/main/.github/workflows/build-binaries.yml) for a comprehensive 18-target example.
## Local development
```bash
# Install maturin
uv tool install maturin
# Build locally
maturin build --release
# Build + install into current venv
maturin develop --release
# Test the installed binary
flowmark-rs --help
```
## PyPI setup checklist
1. **Create the PyPI project** — register `flowmark-rs` on https://pypi.org
2. **Configure trusted publishing** — in PyPI project settings, add a trusted publisher for the GitHub repo + workflow file + environment name (`release`)
3. **Tag a release** — `git tag v0.1.0 && git push --tags` triggers the workflow
4. **Verify** — `uvx flowmark-rs --version` should work within minutes of publish
## Consumer migration
Once published, downstream projects switch with a one-line change:
```diff
- uvx flowmark
+ uvx flowmark-rs
```
Same UX. Same `uvx` ecosystem. Much faster.
## References
- [Maturin user guide](https://www.maturin.rs/) — full documentation
- [Maturin `bin` bindings](https://www.maturin.rs/bindings) — binary-only distribution
- [PyO3/maturin-action](https://github.com/PyO3/maturin-action) — GitHub Actions for cross-platform builds
- [Ruff's pyproject.toml](https://github.com/astral-sh/ruff/blob/main/pyproject.toml) — real-world reference (same pattern)
- [Ruff's build-binaries.yml](https://github.com/astral-sh/ruff/blob/main/.github/workflows/build-binaries.yml) — comprehensive 18-target build matrix
- [Maturin distribution guide](https://www.maturin.rs/distribution.html) — manylinux, cross-compilation, zig
Contributor guide
Research direction
Start by inspecting Cargo.toml and running the documented maturin build --release command locally. Add the specified pyproject.toml and .github/workflows/release.yml, then verify that the workflow builds the listed platform wheels and source distribution. Done means the tagged-release workflow publishes successfully and uvx flowmark-rs --version works from PyPI.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- github-actions, python, rust
- Domain
- build-system, ci-cd, cli, release
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Clearly specified
- Newbie friendliness
- 55/100