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
14 changes: 13 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
.git/
.worktrees/
.opencode/
target/
.tmp/
deps/LAGraph/build/
practice-templates/
la-n-egg-rpq/

# Local-only graph datasets (gitignored too; never needed in image)
pathrex/tests/testdata/la-rpq/
pathrex/tests/testdata/wikidata/
pathrex/tests/testdata/yago/

# LAGraph in-tree build artifacts (the cmake-rs build uses $OUT_DIR)
pathrex-sys/deps/LAGraph/build/

# Benchmark output
bench_criterion/
bench_results.json
bench_checkpoint.json
2 changes: 1 addition & 1 deletion .gitattributes
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
tests/testdata/** filter=lfs diff=lfs merge=lfs -text
pathrex/tests/testdata/** filter=lfs diff=lfs merge=lfs -text
AGENTS.md merge=union
build.rs merge=union
30 changes: 2 additions & 28 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,19 +22,6 @@ jobs:
sudo apt-get update
sudo apt-get install -y cmake libclang-dev clang

- name: Build and install SuiteSparse:GraphBLAS
run: |
git clone --depth 1 https://github.com/DrTimothyAldenDavis/GraphBLAS.git
cd GraphBLAS
make compact
sudo make install

- name: Build and install LAGraph
run: |
cd deps/LAGraph
make
sudo make install

- name: Install Rust toolchain (stable)
run: |
rustup update stable
Expand Down Expand Up @@ -67,24 +54,11 @@ jobs:
sudo apt-get update
sudo apt-get install -y cmake libclang-dev clang

- name: Build and install SuiteSparse:GraphBLAS
run: |
git clone --depth 1 https://github.com/DrTimothyAldenDavis/GraphBLAS.git
cd GraphBLAS
make compact
sudo make install

- name: Build and install LAGraph
run: |
cd deps/LAGraph
make
sudo make install

- name: Install Rust toolchain
run: rustup update ${{ matrix.toolchain }} && rustup default ${{ matrix.toolchain }}

- name: Build (with regenerated bindings)
run: cargo build --features regenerate-bindings --verbose
run: cargo build --workspace --features pathrex-sys/regenerate-bindings --verbose

- name: Test
run: LD_LIBRARY_PATH=/usr/local/lib cargo test --verbose
run: cargo test --workspace --verbose
80 changes: 76 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ on:
- 'v*.*.*'

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
GHCR_IMAGE: ghcr.io/${{ github.repository }}
DOCKERHUB_IMAGE: docker.io/vanyaglazunov/pathrex

jobs:
docker:
Expand All @@ -30,15 +30,25 @@ jobs:
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Log in to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}

- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# docker/metadata-action expands each tag against every image
# listed here, so a single push step publishes both registries.
images: |
${{ env.GHCR_IMAGE }}
${{ env.DOCKERHUB_IMAGE }}
tags: |
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
Expand All @@ -54,3 +64,65 @@ jobs:
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

publish-crates:
name: Publish to crates.io
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
submodules: recursive
lfs: true

- name: Install system dependencies
run: |
sudo apt-get update
sudo apt-get install -y cmake libclang-dev clang

- name: Install Rust toolchain (stable)
run: |
rustup update stable
rustup default stable

- name: Read pathrex-sys version
id: sys_version
run: |
ver=$(cargo metadata --no-deps --format-version 1 \
| jq -r '.packages[] | select(.name == "pathrex-sys") | .version')
echo "version=$ver" >> "$GITHUB_OUTPUT"
echo "pathrex-sys version: $ver"

- name: Publish pathrex-sys (if not already published)
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
SYS_VERSION: ${{ steps.sys_version.outputs.version }}
run: |
if cargo search pathrex-sys --limit 1 \
| grep -q "^pathrex-sys = \"${SYS_VERSION}\""; then
echo "pathrex-sys ${SYS_VERSION} already on crates.io, skipping"
else
cargo publish -p pathrex-sys
fi

- name: Wait for pathrex-sys to appear on crates.io
env:
SYS_VERSION: ${{ steps.sys_version.outputs.version }}
# crates.io publishes update the sparse index within seconds, but the
# exact propagation time is unpredictable. Poll up to 5 minutes.
run: |
for i in $(seq 1 30); do
if cargo search pathrex-sys --limit 1 \
| grep -q "^pathrex-sys = \"${SYS_VERSION}\""; then
echo "pathrex-sys ${SYS_VERSION} is live on crates.io"
exit 0
fi
echo "attempt $i: pathrex-sys ${SYS_VERSION} not yet indexed, sleeping 10s"
sleep 10
done
echo "pathrex-sys ${SYS_VERSION} did not appear on crates.io within 5 minutes" >&2
exit 1

- name: Publish pathrex
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish -p pathrex
2 changes: 1 addition & 1 deletion .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[submodule "deps/LAGraph"]
path = deps/LAGraph
path = pathrex-sys/deps/LAGraph
url = https://github.com/SparseLinearAlgebra/LAGraph.git
164 changes: 107 additions & 57 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ pathrex/
│ ├── mm_tests.rs # Integration tests for MatrixMarket format
│ ├── nfarpq_tests.rs # Integration tests for NfaRpqEvaluator
│ └── rpqmatrix_tests.rs # Integration tests for matrix-plan RPQ evaluator
├── deps/
├── pathrex-sys/deps/
│ └── LAGraph/ # Git submodule (SparseLinearAlgebra/LAGraph)
└── .github/workflows/ci.yml # CI: build GraphBLAS + LAGraph, cargo build & test
```
Expand All @@ -53,67 +53,84 @@ pathrex/

| Dependency | Purpose |
|---|---|
| **SuiteSparse:GraphBLAS** | Sparse matrix engine (`libgraphblas`) |
| **LAGraph** | Graph algorithm library on top of GraphBLAS (`liblagraph`) |
| **cmake** | Building LAGraph from source |
| **cmake** | Building GraphBLAS and LAGraph from source |
| **git** | Fetching pinned GraphBLAS source at build time |
| **C/C++ toolchain** | Compiling GraphBLAS and LAGraph (gcc or clang) |
| **OpenMP runtime** | Linked dynamically: `libgomp` on Linux, `libomp` on macOS, `/openmp` on MSVC |
| **libclang-dev / clang** | Required by `bindgen` when `regenerate-bindings` feature is active |

SuiteSparse:GraphBLAS no longer needs to be installed system-wide. It is
fetched, built statically, and linked into the binary by `pathrex-sys/build.rs`.

### Building

```bash
# Ensure submodules are present
# Ensure the LAGraph submodule is present
git submodule update --init --recursive

# Build and install SuiteSparse:GraphBLAS system-wide
git clone --depth 1 https://github.com/DrTimothyAldenDavis/GraphBLAS.git
cd GraphBLAS && make compact && sudo make install && cd ..

# Build LAGraph inside the submodule (no system-wide install required)
cd deps/LAGraph && make && cd ../..

# Build pathrex
# Build pathrex. First cold build clones GraphBLAS and runs cmake; takes
# ~2-10 minutes depending on core count. Subsequent builds reuse the
# GraphBLAS source tree under target/.../graphblas-src/ and the cmake
# build dir, so they are incremental.
cargo build

# Run tests
LD_LIBRARY_PATH=deps/LAGraph/build/src:deps/LAGraph/build/experimental:/usr/local/lib cargo test
# Run tests (no LD_LIBRARY_PATH needed — everything but the OpenMP
# runtime is statically linked)
cargo test --workspace
```

### How `build.rs` handles linking

[`build.rs`](build.rs) performs two jobs:

1. **Native linking.** It emits six Cargo directives:
- `cargo:rustc-link-lib=dylib=graphblas` — dynamically links `libgraphblas`.
- `cargo:rustc-link-search=native=/usr/local/lib` — adds the system GraphBLAS
install path to the native library search path.
- `cargo:rustc-link-lib=dylib=lagraph` — dynamically links `liblagraph`.
- `cargo:rustc-link-search=native=deps/LAGraph/build/src` — adds the
submodule's core build output to the native library search path.
- `cargo:rustc-link-lib=dylib=lagraphx` — dynamically links `liblagraphx`
(experimental algorithms).
- `cargo:rustc-link-search=native=deps/LAGraph/build/experimental` —
adds the experimental build output to the native library search path.

LAGraph does **not** need to be installed system-wide; building the submodule
in `deps/LAGraph/` is sufficient for compilation and linking.
SuiteSparse:GraphBLAS **must** be installed system-wide (`sudo make install`).

At **runtime** the OS dynamic linker (`ld.so`) does not use Cargo's link
search paths — it only consults `LD_LIBRARY_PATH`, `rpath`, and the system
library cache. Set `LD_LIBRARY_PATH=/usr/local/lib` after a system-wide
LAGraph install, or include the submodule build paths if not installing
system-wide.

2. **Optional FFI binding regeneration** (feature `regenerate-bindings`).
When the feature is active, [`regenerate_bindings()`](build.rs:20) runs
`bindgen` against `deps/LAGraph/include/LAGraph.h` and
`deps/LAGraph/include/LAGraphX.h` (always from the submodule — no system
path search), plus `GraphBLAS.h` (searched in
`/usr/local/include/suitesparse` and `/usr/include/suitesparse`). The
generated Rust file is written to
[`src/lagraph_sys_generated.rs`](src/lagraph_sys_generated.rs). Only a
curated allowlist of GraphBLAS/LAGraph types and functions is exposed
(see the `allowlist_*` calls in [`build.rs`](build.rs:59)).
### How `pathrex-sys/build.rs` handles linking

[`pathrex-sys/build.rs`](pathrex-sys/build.rs) performs three jobs:

1. **GraphBLAS fetch.** Clones SuiteSparse:GraphBLAS at the pin defined by
the `GRAPHBLAS_TAG` constant (currently `v10.3.1`) into
`$OUT_DIR/graphblas-src/` via `git clone --depth=1 --branch <tag>`. A
sentinel file `<dir>/.pathrex-fetched` containing the tag string marks
a completed clone; if the pin is bumped, the sentinel mismatches and
the clone is wiped and retried. If the directory exists without a
sentinel (interrupted earlier clone), it is also wiped before retrying.

2. **Native build + linking.** Drives cmake twice — once for GraphBLAS,
once for the `pathrex-sys/deps/LAGraph` submodule:

- GraphBLAS flags: `BUILD_SHARED_LIBS=OFF`, `BUILD_STATIC_LIBS=ON`,
`GRAPHBLAS_BUILD_STATIC_LIBS=ON` (belt-and-braces),
`GRAPHBLAS_USE_JIT=OFF` (no runtime C compiler required),
`GRAPHBLAS_COMPACT=OFF` (full FactoryKernels for performance),
`GRAPHBLAS_USE_OPENMP=ON`, `GRAPHBLAS_USE_CUDA=OFF`,
`SUITESPARSE_DEMOS=OFF`, `BUILD_TESTING=OFF`, `Release` profile.
- LAGraph flags: `BUILD_SHARED_LIBS=OFF`, `BUILD_STATIC_LIBS=ON`,
`BUILD_TESTING=OFF`, plus `CMAKE_PREFIX_PATH=<graphblas_install>`
and `GRAPHBLAS_ROOT=<graphblas_install>` so LAGraph's
`find_package(GraphBLAS)` picks up our static build instead of any
system one.
- Static archives land in `$OUT_DIR/.../out/lib/` (or `lib64/` on
Fedora-family distros — both candidates are probed by `pick_libdir`).
- Emits `cargo:rustc-link-lib=static=lagraphx`,
`cargo:rustc-link-lib=static=lagraph`,
`cargo:rustc-link-lib=static=graphblas`. Order matters: `lagraphx`
references symbols from `lagraph`'s utility module; both reference
`graphblas`.
- Emits OS-specific runtime libraries: `gomp`+`pthread`+`dl`+`m` on
Linux, `omp`+`pthread` on macOS, nothing explicit on MSVC. Override
via `RUSTFLAGS` if your toolchain ships a different OpenMP runtime
(e.g. `libomp` on Linux + clang).

3. **docs.rs guard.** If the `DOCS_RS` environment variable is set, the
entire native build is skipped. docs.rs sandboxes block all network
access (so the `git clone` would fail) and have strict time/memory
limits; rustdoc only needs to compile Rust code, not link or execute
it.

4. **Optional FFI binding regeneration** (feature `regenerate-bindings`).
When the feature is active, `regenerate_bindings()` runs `bindgen`
against `deps/LAGraph/include/LAGraph.h`,
`deps/LAGraph/include/LAGraphX.h`, and the GraphBLAS install tree's
`include/suitesparse/GraphBLAS.h`. The generated Rust file is written
to [`pathrex-sys/src/lagraph_sys_generated.rs`](pathrex-sys/src/lagraph_sys_generated.rs).
Only a curated allowlist of GraphBLAS/LAGraph types and functions is
exposed (see the `allowlist_*` calls in `pathrex-sys/build.rs`).

### Feature flags

Expand Down Expand Up @@ -533,9 +550,42 @@ require the native libraries to be present.
The GitHub Actions workflow ([`.github/workflows/ci.yml`](.github/workflows/ci.yml))
runs on every push and PR across `stable`, `beta`, and `nightly` toolchains:

1. Checks out with `submodules: recursive`.
2. Installs cmake, libclang-dev, clang.
3. Builds and installs SuiteSparse:GraphBLAS from source (`sudo make install`).
4. Builds and installs LAGraph from the submodule (`sudo make install`).
5. `cargo build --features regenerate-bindings` — rebuilds FFI bindings.
6. `LD_LIBRARY_PATH=/usr/local/lib cargo test --verbose` — runs the full test suite.
1. Checks out with `submodules: recursive` and `lfs: true`.
2. Installs `cmake`, `libclang-dev`, `clang` via apt.
3. `cargo build --workspace --features pathrex-sys/regenerate-bindings` —
`pathrex-sys/build.rs` clones GraphBLAS at the pinned tag, builds it
statically, builds LAGraph statically against it, and regenerates FFI
bindings.
4. `cargo test --workspace --verbose` — runs the full test suite. No
`LD_LIBRARY_PATH` is needed because GraphBLAS and LAGraph are linked
statically; only the OpenMP runtime (`libgomp`) is dynamic and is
already on the default loader path.

## Releasing

Pushing a `v*.*.*` tag triggers [`.github/workflows/release.yml`](.github/workflows/release.yml):

1. **`docker` job** — builds the image once and pushes the same tags
(`{version}`, `{major}.{minor}`, `{major}`, `latest`) to two
registries:
- `ghcr.io/sparselinearalgebra/pathrex` (authenticated via
`GITHUB_TOKEN`, no extra setup needed).
- `docker.io/vanyaglazunov/pathrex` (authenticated via
`DOCKERHUB_USERNAME` + `DOCKERHUB_TOKEN` repository secrets).
2. **`publish-crates` job** — publishes both crates to crates.io in the
correct order:
- Reads `pathrex-sys` version via `cargo metadata`.
- Skips publishing `pathrex-sys` if that version is already on the
registry (allows re-tagging when only `pathrex` changed).
- Polls `cargo search` until the new `pathrex-sys` is indexed
(up to 5 minutes), then publishes `pathrex` against it.
- Requires the `CARGO_REGISTRY_TOKEN` repository secret.

### Manual dry-run

To verify the publish path without uploading:

```bash
cargo publish --dry-run -p pathrex-sys
# (pathrex dry-run requires pathrex-sys to be on crates.io first)
```
Loading
Loading