diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e821bd5..87e0f60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,12 @@ # ────────────────────────────────────────────────────────────── # f2a CI — Lint, build, test on every push / PR # Based on CocoRoF/googer proven workflow pattern. +# +# Optimised for speed: +# - Rust target + registry cached (Swatinem/rust-cache) +# - pip cached +# - lint & test run in parallel +# - matrix trimmed: full OS × Python only on main; PR = Linux-only # ────────────────────────────────────────────────────────────── name: CI @@ -10,7 +16,29 @@ on: pull_request: branches: [main] +env: + CARGO_INCREMENTAL: "1" + CARGO_NET_RETRY: "10" + RUSTUP_MAX_RETRIES: "10" + jobs: + # ── Version consistency check (fast, no build) ──────────── + check-versions: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Verify pyproject.toml == Cargo.toml versions + run: | + PY_VER=$(grep -oP '^version\s*=\s*"\K[^"]+' pyproject.toml) + RS_VER=$(grep -oP '^version\s*=\s*"\K[^"]+' Cargo.toml) + echo "pyproject.toml = $PY_VER" + echo "Cargo.toml = $RS_VER" + if [ "$PY_VER" != "$RS_VER" ]; then + echo "::error::Version mismatch! pyproject.toml=$PY_VER vs Cargo.toml=$RS_VER — update both files." + exit 1 + fi + # ── Lint (Rust + Python) ────────────────────────────────── lint: runs-on: ubuntu-latest @@ -22,6 +50,11 @@ jobs: with: components: clippy, rustfmt + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + cache-on-failure: true + - name: cargo fmt --check run: cargo fmt --all -- --check @@ -30,12 +63,18 @@ jobs: # ── Test (cross-platform × multi-Python) ────────────────── test: - needs: lint + # Run in parallel with lint (no 'needs') strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-14, windows-latest] - python-version: ["3.10", "3.11", "3.12", "3.13"] + os: [ubuntu-latest] + python-version: ["3.10", "3.12"] + include: + # Spot-check other platforms with one Python version + - os: macos-14 + python-version: "3.12" + - os: windows-latest + python-version: "3.12" runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 @@ -43,11 +82,25 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + key: ${{ matrix.os }}-py${{ matrix.python-version }} + cache-on-failure: true + - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} + - name: Pip cache + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: pip-${{ matrix.os }}-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} + restore-keys: | + pip-${{ matrix.os }}-py${{ matrix.python-version }}- + - name: Install package and test deps run: | python -m pip install --upgrade pip diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index c7012a9..022789e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -67,7 +67,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.13"] steps: - uses: actions/checkout@v4 @@ -79,6 +79,20 @@ jobs: - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable + - name: Rust cache + uses: Swatinem/rust-cache@v2 + with: + key: publish-py${{ matrix.python-version }} + cache-on-failure: true + + - name: Pip cache + uses: actions/cache@v4 + with: + path: ~/.cache/pip + key: pip-publish-py${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }} + restore-keys: | + pip-publish-py${{ matrix.python-version }}- + - name: Install package and test deps run: | python -m pip install --upgrade pip diff --git a/Cargo.toml b/Cargo.toml index c57aa6c..1971cc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "f2a" -version = "1.0.2" +version = "1.0.3" edition = "2021" description = "f2a computation core -- Rust engine with PyO3 bindings" license = "Apache-2.0" @@ -58,6 +58,11 @@ indexmap = { version = "2", features = ["serde"] } [profile.release] opt-level = 3 + +# Faster dev/test builds (used by `pip install .` which defaults to debug) +[profile.dev] +opt-level = 1 # enough optimisation to keep tests realistic +debug = false # skip DWARF → faster link lto = "fat" codegen-units = 1 strip = true diff --git a/pyproject.toml b/pyproject.toml index e248bbd..ab6fa78 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "f2a" -version = "1.0.2" +version = "1.0.3" description = "File to Analysis -- Automatically perform statistical analysis from any data source (Rust-powered)" license = { text = "Apache-2.0" } readme = "README.md" diff --git a/python/f2a/_version.py b/python/f2a/_version.py index 5becc17..86f2862 100644 --- a/python/f2a/_version.py +++ b/python/f2a/_version.py @@ -1 +1,7 @@ -__version__ = "1.0.0" +from importlib.metadata import version, PackageNotFoundError + +try: + __version__: str = version("f2a") +except PackageNotFoundError: + # Fallback for editable / dev installs where metadata isn't available yet + __version__ = "0.0.0-dev" diff --git a/tests/test_core.py b/tests/test_core.py index 5218b07..7ebb44b 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -16,13 +16,20 @@ def test_import(): def test_version(): import f2a - assert f2a.__version__ == "1.0.0" + # Version must be a valid semver-like string, not the dev fallback + assert f2a.__version__ != "0.0.0-dev" + parts = f2a.__version__.split(".") + assert len(parts) >= 2, f"Unexpected version format: {f2a.__version__}" def test_rust_core_version(): from f2a._core import version + import f2a - assert version() == "1.0.0" + # Rust core version (from Cargo.toml) must match Python package version (from pyproject.toml) + assert version() == f2a.__version__, ( + f"Version mismatch: Rust core={version()}, Python package={f2a.__version__}" + ) def test_rust_core_configs():