Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 0 additions & 42 deletions .github/workflows/build.yml

This file was deleted.

151 changes: 151 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
name: ci

# Gates (test, lint) run first and BLOCK the build jobs (needs:). The coverage job is
# informational (continue-on-error, in no job's needs) so a coverage/token hiccup never
# reddens the pipeline. Windows uses nightly Rust (ext-php-rs abi_vectorcall).
on:
push:
pull_request:
workflow_dispatch: {}

permissions:
contents: read

env:
CARGO_TERM_COLOR: always

jobs:
test:
name: Rust tests + clippy (stable, PHP 8.4 NTS)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# cargo test / clippy --all-targets compile sdsearch-php, whose ext-php-rs bindgen step
# links against PHP internals — so PHP dev headers + clang are required even to test.
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
tools: none
extensions: none
- name: Assert PHP build is NTS
run: php -v | grep -q 'NTS' || { echo "::error::expected an NTS PHP build, got:"; php -v; exit 1; }
- name: Install clang (ext-php-rs bindgen)
run: sudo apt-get update && sudo apt-get install -y clang libclang-dev
- uses: dtolnay/rust-toolchain@stable
with:
components: clippy
- uses: Swatinem/rust-cache@v2
- run: cargo test
- run: cargo clippy --all-targets -- -D warnings

lint:
name: Lint & supply-chain (fmt, audit, deny, machete)
runs-on: ubuntu-latest
# Lightweight: fmt reads source; audit/deny read Cargo.lock; machete parses manifests.
# None need clang or PHP (clippy runs against a real compile in the `test` job).
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt
- run: cargo fmt --all --check
- name: Install cargo-audit, cargo-deny, cargo-machete
uses: taiki-e/install-action@v2
with:
tool: cargo-audit,cargo-deny,cargo-machete
- run: cargo audit
- run: cargo deny check
- run: cargo machete

coverage:
name: Coverage (sdsearch-core -> octocov)
runs-on: ubuntu-latest
# Informational: never blocks the pipeline and is in no job's `needs`. sdsearch-core has no
# PHP dependency, so this job needs neither clang nor PHP.
continue-on-error: true
permissions:
contents: write # octocov commits the coverage badge to main
pull-requests: write # octocov posts the PR comment
actions: read # octocov reads the artifact datastore
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with:
components: llvm-tools-preview
- uses: Swatinem/rust-cache@v2
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
# Skip the interprocess-lock test: `cargo llvm-cov` runs `cargo test --tests`, which does not
# build the stream_writer example that test needs, so it fails only under llvm-cov.
- name: Measure coverage (sdsearch-core)
run: |
mkdir -p coverage
cargo llvm-cov -p sdsearch-core --lcov --output-path coverage/lcov.info \
-- --skip write_lock_excludes_a_second_process
- name: octocov (PR comment + main badge)
uses: k1LoW/octocov-action@v1
with:
github-token: ${{ secrets.GITHUB_TOKEN }}

build-linux:
name: Build + smoke (Linux, PHP 8.4 NTS)
runs-on: ubuntu-latest
needs: [test, lint]
steps:
- uses: actions/checkout@v4
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
tools: none
extensions: none
- name: Assert PHP build is NTS
run: php -v | grep -q 'NTS' || { echo "::error::expected an NTS PHP build, got:"; php -v; exit 1; }
- name: Install clang (ext-php-rs bindgen)
run: sudo apt-get update && sudo apt-get install -y clang libclang-dev
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- run: cargo build -p sdsearch-php --release
- name: Smoke test (extension loads + sdsearch_version)
run: php -d extension="$(pwd)/target/release/libsdsearch.so" tests/smoke.php
- name: Stage Linux artifact
run: |
mkdir -p dist/linux
cp target/release/libsdsearch.so dist/linux/sdsearch.so
- uses: actions/upload-artifact@v4
with:
name: sdsearch-linux-x64-php84nts
path: dist/linux/*
if-no-files-found: error

build-windows:
name: Build + smoke (Windows, nightly, PHP 8.4 NTS)
runs-on: windows-latest
needs: [test, lint]
# RUSTUP_TOOLCHAIN takes precedence over rust-toolchain.toml (which pins stable); ext-php-rs
# needs nightly on Windows for abi_vectorcall.
env:
RUSTUP_TOOLCHAIN: nightly
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- uses: shivammathur/setup-php@v2
with:
php-version: '8.4'
- uses: KyleMayes/install-llvm-action@v2
with:
version: "18.1"
- run: cargo build -p sdsearch-php --release
- name: Smoke test (extension loads + sdsearch_version)
run: php -d extension="${{ github.workspace }}\target\release\sdsearch.dll" tests\smoke.php
- name: Stage Windows artifact
shell: pwsh
run: |
New-Item -ItemType Directory -Force -Path dist\windows | Out-Null
Copy-Item target\release\sdsearch.dll dist\windows\sdsearch.dll
- uses: actions/upload-artifact@v4
with:
name: sdsearch-windows-x64-php84nts
path: dist/windows/*
if-no-files-found: error
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@
/.superpowers/
/.remember/
/.claude/
/coverage
29 changes: 29 additions & 0 deletions .octocov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# octocov: self-hosted coverage reporting (no external SaaS).
# - On PRs: posts/updates a comment with total + per-file coverage and the delta vs main.
# - On main: stores the report to a GitHub Actions artifact datastore and commits the badge.
coverage:
paths:
- coverage/lcov.info
badge:
path: docs/coverage.svg

# Commit the regenerated badge back to main. "[skip ci]" prevents the commit from re-triggering CI.
push:
if: is_default_branch
message: "docs: update coverage badge [skip ci]"

# Store the current report on main so PRs can diff against it. artifact:// = native Actions artifact,
# no branch, no external service.
report:
if: is_default_branch
datastores:
- artifact://${GITHUB_REPOSITORY}

# On PRs, read the stored main report to compute the delta.
diff:
if: is_pull_request
datastores:
- artifact://${GITHUB_REPOSITORY}

comment:
if: is_pull_request
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# sdsearch

[![build](https://github.com/InvGate/php-sdsearch/actions/workflows/build.yml/badge.svg)](https://github.com/InvGate/php-sdsearch/actions/workflows/build.yml)
[![CI](https://github.com/InvGate/php-sdsearch/actions/workflows/ci.yml/badge.svg)](https://github.com/InvGate/php-sdsearch/actions/workflows/ci.yml)
[![coverage](https://raw.githubusercontent.com/InvGate/php-sdsearch/main/docs/coverage.svg)](https://github.com/InvGate/php-sdsearch/actions/workflows/ci.yml)
[![license](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](LICENSE)

A native Rust engine that reads **and** writes the [Zend Search Lucene](https://framework.zend.com/manual/1.12/en/zend.search.lucene.html)
Expand Down Expand Up @@ -101,10 +102,14 @@ them as a shape-of-the-win indicator, not a guarantee.

## Continuous integration

`.github/workflows/build.yml` builds and tests on both **Linux** and **Windows** on every
push/PR: `cargo test -p sdsearch-core`, a release build of the PHP extension, and a smoke
test that loads it into PHP and calls into it. No ZSL install is needed in CI — it only
exercises the fixture-backed test suite and the extension build/load.
The pipeline in `.github/workflows/ci.yml` runs on every push/PR:

- **`test`** — `cargo test` (whole workspace) and `cargo clippy --all-targets -- -D warnings`.
- **`lint`** — `cargo fmt --all --check` plus supply-chain checks: `cargo audit`, `cargo deny`, and `cargo machete`.
- **`coverage`** — measures `sdsearch-core` coverage with `cargo-llvm-cov` and reports it via [octocov](https://github.com/k1LoW/octocov): a comment on each PR with the coverage delta vs `main`, plus the coverage badge above. It is informational and never blocks the pipeline.
- **`build-linux` / `build-windows`** — release-build the PHP extension and run a smoke test that loads it into PHP, then upload the `.so`/`.dll` as build artifacts. These run only after `test` and `lint` pass.

No ZSL install is needed in CI — it only exercises the fixture-backed test suite and the extension build/load.

## License

Expand Down
35 changes: 35 additions & 0 deletions deny.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# cargo-deny policy for sdsearch (Apache-2.0 project).
# License allow-list derived from `cargo deny list` over the dependency tree.
[graph]
all-features = true

[advisories]
version = 2
yanked = "deny"

[licenses]
version = 2
# Permissive licenses present across the dependency tree (all verified present via `cargo deny list`).
allow = [
"Apache-2.0",
"Apache-2.0 WITH LLVM-exception",
"MIT",
"MIT-0",
"BSD-3-Clause",
"ISC",
"Zlib",
"Unicode-3.0",
"CC0-1.0",
"CDLA-Permissive-2.0",
"Unlicense",
"bzip2-1.0.6",
]
confidence-threshold = 0.9

[bans]
# The dependency tree carries a few duplicated transitive versions (e.g. shlex); not worth failing CI over.
multiple-versions = "allow"

[sources]
unknown-registry = "deny"
unknown-git = "deny"
15 changes: 12 additions & 3 deletions sdsearch-core/examples/append_writer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,12 @@ fn default_true() -> bool {

fn main() {
let args: Vec<String> = std::env::args().collect();
let index_dir = args.get(1).expect("usage: append_writer <index_dir> <docs.json>");
let docs_path = args.get(2).expect("usage: append_writer <index_dir> <docs.json>");
let index_dir = args
.get(1)
.expect("usage: append_writer <index_dir> <docs.json>");
let docs_path = args
.get(2)
.expect("usage: append_writer <index_dir> <docs.json>");

let raw = std::fs::read_to_string(docs_path).expect("could not read docs.json");
let spec: Spec = serde_json::from_str(&raw).expect("invalid docs.json");
Expand All @@ -54,7 +58,12 @@ fn main() {
"unindexed" => FieldKind::UnIndexed,
other => panic!("unknown kind: {other}"),
};
WriterField { name: f.name, value: f.value, kind, stored: f.stored }
WriterField {
name: f.name,
value: f.value,
kind,
stored: f.stored,
}
})
.collect(),
})
Expand Down
8 changes: 6 additions & 2 deletions sdsearch-core/examples/diff_read.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,12 @@ fn params(text: &str) -> QueryParams {

fn main() {
let args: Vec<String> = std::env::args().collect();
let dir = args.get(1).expect("usage: diff_read <index_dir> <queries.json>");
let qpath = args.get(2).expect("usage: diff_read <index_dir> <queries.json>");
let dir = args
.get(1)
.expect("usage: diff_read <index_dir> <queries.json>");
let qpath = args
.get(2)
.expect("usage: diff_read <index_dir> <queries.json>");

let raw = std::fs::read_to_string(qpath).expect("could not read queries.json");
let spec: QSpec = serde_json::from_str(&raw).expect("invalid queries.json");
Expand Down
Loading
Loading