diff --git a/.dockerignore b/.dockerignore index faf37b5..a82079d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -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 diff --git a/.gitattributes b/.gitattributes index e174acd..b951d22 100644 --- a/.gitattributes +++ b/.gitattributes @@ -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 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 974638a..1b4977a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -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 @@ -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 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c333112..00b4e25 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -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: @@ -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}} @@ -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 diff --git a/.gitmodules b/.gitmodules index ca3fb6e..f890142 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "deps/LAGraph"] - path = deps/LAGraph + path = pathrex-sys/deps/LAGraph url = https://github.com/SparseLinearAlgebra/LAGraph.git diff --git a/AGENTS.md b/AGENTS.md index 22dc2df..2e8a084 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 ``` @@ -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 `. A + sentinel file `/.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=` + and `GRAPHBLAS_ROOT=` 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 @@ -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) +``` diff --git a/Cargo.toml b/Cargo.toml index ae9cf61..e2e6f30 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,38 +1,11 @@ -[package] -name = "pathrex" -version = "0.1.0" -edition = "2024" - -[dependencies] -csv = "1.4.0" -egg = "0.10.0" -libc = "0.2" -memmap2 = "0.9" -oxrdf = "0.3.3" -oxttl = "0.2.3" -rayon = "1" -rustfst = "1.2" -spargebra = "0.4.6" -thiserror = "1.0" - -clap = { version = "4", features = ["derive"], optional = true } -serde = { version = "1", features = ["derive"], optional = true } -serde_json = { version = "1", optional = true } -chrono = { version = "0.4", features = ["serde"], optional = true } -criterion = { version = "0.5", optional = true } -tempfile = { version = "3", optional = true } - -[features] -regenerate-bindings = ["bindgen"] -bench = ["clap", "serde", "serde_json", "chrono", "criterion", "tempfile"] +[workspace] +resolver = "2" +members = ["pathrex-sys", "pathrex"] -[dev-dependencies] -tempfile = "3" - -[build-dependencies] -bindgen = { version = "0.71", optional = true } - -[[bin]] -name = "pathrex" -path = "src/bin/pathrex.rs" -required-features = ["bench"] +[workspace.package] +edition = "2024" +rust-version = "1.85" +license = "MIT" +repository = "https://github.com/SparseLinearAlgebra/pathrex" +homepage = "https://github.com/SparseLinearAlgebra/pathrex" +authors = ["Pathrex contributors"] diff --git a/Dockerfile b/Dockerfile index 0d8f56a..4cc12a4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -8,42 +8,28 @@ RUN apt-get update \ cmake \ git \ libclang-dev \ - make \ - pkg-config \ && rm -rf /var/lib/apt/lists/* WORKDIR /src COPY . . -RUN git clone --depth 1 https://github.com/DrTimothyAldenDavis/GraphBLAS.git /tmp/GraphBLAS \ - && make -C /tmp/GraphBLAS compact \ - && make -C /tmp/GraphBLAS install \ - && mkdir -p deps/LAGraph/build \ - && make -C deps/LAGraph \ - && cargo build --release --bin pathrex --features "bench,regenerate-bindings" +RUN cargo build --release --bin pathrex --features bench FROM debian:bookworm-slim AS runtime RUN apt-get update \ && apt-get install -y --no-install-recommends \ ca-certificates \ - gcc \ - libc6-dev \ libgomp1 \ && rm -rf /var/lib/apt/lists/* WORKDIR /work COPY --from=builder /src/target/release/pathrex /usr/local/bin/pathrex -COPY --from=builder /usr/local/lib/libgraphblas.so* /usr/local/lib/ -COPY --from=builder /src/deps/LAGraph/build/src/liblagraph.so* /usr/local/lib/ -COPY --from=builder /src/deps/LAGraph/build/experimental/liblagraphx.so* /usr/local/lib/ COPY --from=builder /src/docker/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh RUN chmod +x /usr/local/bin/docker-entrypoint.sh -ENV LD_LIBRARY_PATH=/usr/local/lib - ENTRYPOINT ["/usr/local/bin/docker-entrypoint.sh"] CMD ["--help"] diff --git a/README.md b/README.md index bb95c20..93dbe3e 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,15 @@ # pathrex +[![Crates.io](https://img.shields.io/crates/v/pathrex.svg)](https://crates.io/crates/pathrex) +[![Docs.rs](https://docs.rs/pathrex/badge.svg)](https://docs.rs/pathrex) +[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![CI](https://github.com/SparseLinearAlgebra/pathrex/actions/workflows/ci.yml/badge.svg)](https://github.com/SparseLinearAlgebra/pathrex/actions/workflows/ci.yml) [![Container](https://img.shields.io/badge/ghcr.io-pathrex-blue?logo=docker)](https://github.com/SparseLinearAlgebra/pathrex/pkgs/container/pathrex) +[![Docker Hub](https://img.shields.io/docker/v/vanyaglazunov/pathrex?label=docker.io&logo=docker)](https://hub.docker.com/r/vanyaglazunov/pathrex) **Pathrex** is a Rust library and CLI for evaluating and benchmarking **Path Queries** over edge-labeled graphs. + ## Features - **Two RPQ evaluators** out of the box: @@ -117,4 +122,4 @@ q2,?x (|)+ ?y ## License -See [`LICENSE`](LICENSE). +Licensed under the MIT License. See [`LICENSE`](LICENSE). diff --git a/build.rs b/build.rs deleted file mode 100644 index 95243fb..0000000 --- a/build.rs +++ /dev/null @@ -1,113 +0,0 @@ -#[cfg(feature = "regenerate-bindings")] -use std::path::PathBuf; - -fn main() { - println!("cargo:rustc-link-lib=dylib=graphblas"); - println!("cargo:rustc-link-search=native=/usr/local/lib"); - println!("cargo:rustc-link-lib=dylib=lagraph"); - println!("cargo:rustc-link-search=native=deps/LAGraph/build/src"); - println!("cargo:rustc-link-lib=dylib=lagraphx"); - println!("cargo:rustc-link-search=native=deps/LAGraph/build/experimental"); - - // ---- Bindgen (only with `regenerate-bindings` feature) ---- - #[cfg(feature = "regenerate-bindings")] - regenerate_bindings(); - - println!("cargo:rerun-if-changed=build.rs"); -} - -#[cfg(feature = "regenerate-bindings")] -fn regenerate_bindings() { - let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); - - let lagraph_include = manifest_dir.join("deps/LAGraph/include"); - assert!( - lagraph_include.join("LAGraph.h").exists(), - "LAGraph.h not found at {}.\n\ - Fetch the submodule:\n git submodule update --init --recursive", - lagraph_include.display() - ); - - let graphblas_include = [ - PathBuf::from("/usr/local/include/suitesparse"), - PathBuf::from("/usr/include/suitesparse"), - ] - .into_iter() - .find(|p| p.join("GraphBLAS.h").exists()) - .unwrap_or_else(|| { - panic!( - "GraphBLAS.h not found.\n\ - Install SuiteSparse:GraphBLAS so headers are in /usr/local/include/suitesparse." - ) - }); - - let bindings = bindgen::Builder::default() - .header( - lagraph_include - .join("LAGraph.h") - .to_str() - .expect("non-utf8 header path"), - ) - .header( - lagraph_include - .join("LAGraphX.h") - .to_str() - .expect("non-utf8 header path"), - ) - .clang_arg(format!("-I{}", graphblas_include.display())) - .clang_arg(format!("-I{}", lagraph_include.display())) - .allowlist_type("GrB_Index") - .allowlist_type("GrB_Matrix") - .allowlist_type("GrB_Vector") - .allowlist_item("GrB_BOOL") - .allowlist_item("GrB_LOR") - .allowlist_item("GrB_LOR_LAND_SEMIRING_BOOL") - .allowlist_item("GrB_Info") - .allowlist_function("GrB_Matrix_new") - .allowlist_function("GrB_Matrix_nvals") - .allowlist_function("GrB_Matrix_dup") - .allowlist_function("GrB_Matrix_free") - .allowlist_function("GrB_Matrix_extractElement_BOOL") - .allowlist_function("GrB_Matrix_build_BOOL") - .allowlist_function("GrB_Vector_new") - .allowlist_function("GrB_Vector_free") - .allowlist_function("GrB_Vector_setElement_BOOL") - .allowlist_function("GrB_Vector_nvals") - .allowlist_function("GrB_Vector_extractTuples_BOOL") - .allowlist_function("GrB_vxm") - .allowlist_item("LAGRAPH_MSG_LEN") - .allowlist_item("RPQMatrixOp") - .allowlist_type("RPQMatrixPlan") - .allowlist_type("LAGraph_Graph") - .allowlist_type("LAGraph_Kind") - .allowlist_function("LAGraph_CheckGraph") - .allowlist_function("LAGraph_Init") - .allowlist_function("LAGraph_Finalize") - .allowlist_function("LAGraph_SetNumThreads") - .allowlist_function("LAGraph_GetNumThreads") - .allowlist_function("LAGraph_New") - .allowlist_function("LAGraph_Delete") - .allowlist_function("LAGraph_Cached_AT") - .allowlist_function("LAGraph_MMRead") - .allowlist_function("LAGraph_RPQMatrix") - .allowlist_function("LAGraph_RPQMatrix_reduce") - .allowlist_function("LAGraph_DestroyRpqMatrixPlan") - .allowlist_function("LAGraph_RPQMatrix_label") - .allowlist_function("LAGraph_RPQMatrix_Free") - .allowlist_function("LAGraph_RegularPathQuery") - .default_enum_style(bindgen::EnumVariation::Rust { - non_exhaustive: false, - }) - .derive_debug(true) - .derive_copy(true) - .layout_tests(false) - // Suppress C-language doc comments so rustdoc does not attempt to - // compile them as Rust doctests. - .generate_comments(false) - .generate() - .expect("bindgen failed to generate bindings"); - - bindings - .write_to_file(manifest_dir.join("src/lagraph_sys_generated.rs")) - .expect("failed to write bindgen output to src/lagraph_sys_generated.rs"); -} diff --git a/pathrex-sys/Cargo.toml b/pathrex-sys/Cargo.toml new file mode 100644 index 0000000..71a3c71 --- /dev/null +++ b/pathrex-sys/Cargo.toml @@ -0,0 +1,42 @@ +[package] +name = "pathrex-sys" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +description = "Native FFI bindings for SuiteSparse:GraphBLAS and LAGraph used by the pathrex crate." +documentation = "https://docs.rs/pathrex-sys" +readme = "README.md" +categories = ["external-ffi-bindings", "science"] +keywords = ["graphblas", "lagraph", "suitesparse", "ffi", "sys"] +links = "lagraph" +include = [ + "Cargo.toml", + "build.rs", + "README.md", + "src/**/*.rs", + # LAGraph submodule source tree (cmake-built by build.rs). + # Excludes data sets, papers, generated docs, Docker assets, and tests + "deps/LAGraph/CMakeLists.txt", + "deps/LAGraph/LICENSE", + "deps/LAGraph/cmake_modules/**", + "deps/LAGraph/Config/**", + "deps/LAGraph/include/**", + "deps/LAGraph/src/**", + "deps/LAGraph/experimental/**", + "deps/LAGraph/deps/json_h/**", +] + +[features] +default = [] +regenerate-bindings = ["dep:bindgen"] + +[build-dependencies] +cmake = "0.1" +bindgen = { version = "0.71", optional = true } + +[package.metadata.docs.rs] +no-default-features = true diff --git a/pathrex-sys/README.md b/pathrex-sys/README.md new file mode 100644 index 0000000..6db536e --- /dev/null +++ b/pathrex-sys/README.md @@ -0,0 +1,51 @@ +# pathrex-sys + +Native FFI bindings for [SuiteSparse:GraphBLAS] and [LAGraph], used by the +[`pathrex`](https://crates.io/crates/pathrex) crate. + +This crate is not intended to be used directly. It exposes a thin Rust +binding layer over the GraphBLAS and LAGraph C APIs. End users should +depend on [`pathrex`](https://crates.io/crates/pathrex) instead, which +provides a safe, idiomatic Rust interface. + +## What this crate does + +At build time, `pathrex-sys/build.rs`: + +1. Clones [SuiteSparse:GraphBLAS] at a pinned tag into `$OUT_DIR` and + builds it as a static library (`libgraphblas.a`) via cmake. +2. Builds the bundled [LAGraph] source (shipped in `deps/LAGraph/`) as a + static library against the GraphBLAS just built. +3. Emits `cargo:rustc-link-lib=static=...` directives so downstream + crates link the static archives. + +The first cold build clones GraphBLAS and runs cmake; it takes roughly +2-10 minutes depending on core count. Subsequent builds reuse the +GraphBLAS source tree under `$OUT_DIR/graphblas-src/` and the cmake build +directory. + +## System requirements + +| Dependency | Purpose | +|---|---| +| **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 | + +## Features + +| Feature | Effect | +|---|---| +| `regenerate-bindings` | Regenerates `src/lagraph_sys_generated.rs` via `bindgen` from the LAGraph and GraphBLAS headers at build time. Requires `libclang`. Without this feature the checked-in bindings are used as-is. | + +## License + +MIT. See [LICENSE](../LICENSE). + +LAGraph is bundled under its own BSD-2-Clause license; see +`deps/LAGraph/LICENSE`. SuiteSparse:GraphBLAS is fetched at build time +under the Apache-2.0 license. + +[SuiteSparse:GraphBLAS]: https://github.com/DrTimothyAldenDavis/GraphBLAS +[LAGraph]: https://github.com/SparseLinearAlgebra/LAGraph diff --git a/pathrex-sys/build.rs b/pathrex-sys/build.rs new file mode 100644 index 0000000..7b7ce96 --- /dev/null +++ b/pathrex-sys/build.rs @@ -0,0 +1,356 @@ +//! Build script for pathrex-sys. +//! +//! Builds SuiteSparse:GraphBLAS and LAGraph from source as static libraries +//! and emits the link directives needed by the generated bindings. +//! +//! ## Source acquisition +//! +//! GraphBLAS is fetched at build time via `git clone --depth 1 --branch +//! ` into `$OUT_DIR/graphblas-src/`. A sentinel file +//! `$OUT_DIR/graphblas-src/.pathrex-fetched` marks a completed clone so that +//! incremental rebuilds skip the network. The pinned tag lives in +//! [`GRAPHBLAS_TAG`]. +//! +//! LAGraph is provided as a git submodule under `deps/LAGraph` +//! +//! ## docs.rs +//! +//! docs.rs sandboxes block all network access and have strict time/memory +//! limits. The `DOCS_RS` environment variable signals that we are running +//! under docs.rs; we skip the entire native build . + +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +// --------------------------------------------------------------------------- +// Configuration constants +// --------------------------------------------------------------------------- + +/// Upstream SuiteSparse:GraphBLAS release we build against. +const GRAPHBLAS_REPO: &str = "https://github.com/DrTimothyAldenDavis/GraphBLAS.git"; +const GRAPHBLAS_TAG: &str = "v10.3.1"; + +/// LAGraph submodule path, relative to this crate's manifest dir. +/// +/// Lives inside the `pathrex-sys` crate so that `cargo package` includes +/// it in the published `.crate` archive (cargo only ships files within +/// the package directory). +const LAGRAPH_REL_PATH: &str = "deps/LAGraph"; + +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rerun-if-env-changed=DOCS_RS"); + + if env::var_os("DOCS_RS").is_some() { + eprintln!("pathrex-sys: detected DOCS_RS, skipping native build"); + return; + } + + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let out_dir = PathBuf::from(env::var("OUT_DIR").expect("OUT_DIR set by Cargo")); + + let lagraph_src = manifest_dir.join(LAGRAPH_REL_PATH); + assert_lagraph_submodule_present(&lagraph_src); + + let graphblas_src = fetch_graphblas(&out_dir); + let graphblas_install = build_graphblas_static(&graphblas_src); + + let lagraph_install = build_lagraph_static(&lagraph_src, &graphblas_install); + + emit_link_directives(&lagraph_install, &graphblas_install); + + // ---- Bindgen (only with `regenerate-bindings` feature) ---- + #[cfg(feature = "regenerate-bindings")] + regenerate_bindings(&graphblas_install); +} + +/// Bail with a error message if `deps/LAGraph` is empty +fn assert_lagraph_submodule_present(lagraph_src: &Path) { + let marker = lagraph_src.join("CMakeLists.txt"); + if !marker.exists() { + panic!( + "LAGraph submodule not initialized: {} does not exist.\n\ + Run: git submodule update --init --recursive", + marker.display() + ); + } +} + +/// Clone SuiteSparse:GraphBLAS at [`GRAPHBLAS_TAG`] into +/// `$OUT_DIR/graphblas-src/`. Returns the path to the source tree. +/// +/// A sentinel file `/.pathrex-fetched` containing the pinned tag marks +/// a completed clone. If the sentinel matches the requested tag, the clone +/// is reused; if the tag has changed, +/// the entire directory is removed and re-cloned +fn fetch_graphblas(out_dir: &Path) -> PathBuf { + let src_dir = out_dir.join("graphblas-src"); + let sentinel = src_dir.join(".pathrex-fetched"); + + if let Ok(contents) = std::fs::read_to_string(&sentinel) { + if contents.trim() == GRAPHBLAS_TAG { + return src_dir; + } + eprintln!( + "pathrex-sys: GraphBLAS pin changed (was '{}', want '{}'), re-fetching", + contents.trim(), + GRAPHBLAS_TAG + ); + std::fs::remove_dir_all(&src_dir).unwrap_or_else(|e| { + panic!( + "failed to remove stale GraphBLAS clone at {}: {e}", + src_dir.display() + ) + }); + } else if src_dir.exists() { + eprintln!( + "pathrex-sys: incomplete GraphBLAS clone at {}, removing", + src_dir.display() + ); + std::fs::remove_dir_all(&src_dir).unwrap_or_else(|e| { + panic!( + "failed to remove incomplete GraphBLAS clone at {}: {e}", + src_dir.display() + ) + }); + } + + eprintln!("pathrex-sys: cloning GraphBLAS {GRAPHBLAS_TAG} into {src_dir:#?}"); + + let status = Command::new("git") + .args([ + "clone", + "--depth=1", + "--branch", + GRAPHBLAS_TAG, + GRAPHBLAS_REPO, + ]) + .arg(&src_dir) + .status() + .unwrap_or_else(|e| { + panic!( + "failed to invoke `git clone` for GraphBLAS: {e}\n\ + Is git installed and on PATH?" + ) + }); + + if !status.success() { + panic!( + "`git clone` of {GRAPHBLAS_REPO} (tag {GRAPHBLAS_TAG}) failed with status {status}.\n\ + If you are offline, pre-populate {} with a checked-out tree.", + src_dir.display() + ); + } + + std::fs::write(&sentinel, GRAPHBLAS_TAG).unwrap_or_else(|e| { + panic!( + "failed to write fetch sentinel at {}: {e}", + sentinel.display() + ) + }); + + src_dir +} + +/// Drive cmake against the fetched GraphBLAS source. Returns the install +/// prefix (i.e. the cmake-rs `dst` directory) — this is where +/// `lib{,64}/libgraphblas.a` and `lib{,64}/cmake/GraphBLAS/GraphBLASConfig.cmake` +/// live afterwards. +fn build_graphblas_static(graphblas_src: &Path) -> PathBuf { + cmake::Config::new(graphblas_src) + .define("BUILD_SHARED_LIBS", "OFF") + .define("BUILD_STATIC_LIBS", "ON") + .define("GRAPHBLAS_BUILD_STATIC_LIBS", "ON") + .define("GRAPHBLAS_USE_JIT", "OFF") + .define("GRAPHBLAS_COMPACT", "OFF") + .define("GRAPHBLAS_USE_OPENMP", "ON") + .define("GRAPHBLAS_USE_CUDA", "OFF") + .define("SUITESPARSE_DEMOS", "OFF") + .define("BUILD_TESTING", "OFF") + .profile("Release") + .build() +} + +/// Drive cmake against the `deps/LAGraph` submodule. Returns the install +/// prefix containing `lib{,64}/{liblagraph.a,liblagraphx.a}`. +fn build_lagraph_static(lagraph_src: &Path, graphblas_install: &Path) -> PathBuf { + cmake::Config::new(lagraph_src) + .define("BUILD_SHARED_LIBS", "OFF") + .define("BUILD_STATIC_LIBS", "ON") + .define("BUILD_TESTING", "OFF") + .define("CMAKE_PREFIX_PATH", graphblas_install) + .define("GRAPHBLAS_ROOT", graphblas_install) + .define("LAGRAPH_USE_OPENMP", "ON") + .profile("Release") + .build() +} + +/// Emit `cargo:rustc-link-*` directives for both the static archives we just +/// built and the runtime libraries they depend on (OpenMP, libm, libdl). +fn emit_link_directives(lagraph_install: &Path, graphblas_install: &Path) { + let lagraph_libdir = pick_libdir(lagraph_install, "liblagraph.a"); + let graphblas_libdir = pick_libdir(graphblas_install, "libgraphblas.a"); + + println!( + "cargo:rustc-link-search=native={}", + lagraph_libdir.display() + ); + println!( + "cargo:rustc-link-search=native={}", + graphblas_libdir.display() + ); + + // Order: lagraphx -> lagraph -> graphblas -> OS libs. + println!("cargo:rustc-link-lib=static=lagraphx"); + println!("cargo:rustc-link-lib=static=lagraph"); + println!("cargo:rustc-link-lib=static=graphblas"); + + link_runtime_libs(); +} + +fn pick_libdir(install_prefix: &Path, archive_name: &str) -> PathBuf { + let candidates = [install_prefix.join("lib"), install_prefix.join("lib64")]; + candidates + .iter() + .find(|p| p.join(archive_name).exists()) + .cloned() + .unwrap_or_else(|| { + panic!("cmake build succeeded but {archive_name} not found in any of: {candidates:?}") + }) +} + +/// Emit the OS-specific runtime libraries pulled in by the static +/// GraphBLAS archive: OpenMP runtime, libm, libdl, and (potentially) +/// libatomic. +fn link_runtime_libs() { + let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default(); + + match target_os.as_str() { + "linux" => { + // Default to gomp; + // users on a pure-clang/libomp toolchain can override with RUSTFLAGS. + println!("cargo:rustc-link-lib=dylib=gomp"); + println!("cargo:rustc-link-lib=dylib=pthread"); + println!("cargo:rustc-link-lib=dylib=dl"); + println!("cargo:rustc-link-lib=dylib=m"); + } + "macos" => { + println!("cargo:rustc-link-lib=dylib=omp"); + println!("cargo:rustc-link-lib=dylib=pthread"); + } + "windows" => { + // Nothing to emit here. + } + other => { + eprintln!( + "warning: pathrex-sys: unknown target OS '{other}', \ + defaulting runtime libs to Linux conventions" + ); + println!("cargo:rustc-link-lib=dylib=gomp"); + println!("cargo:rustc-link-lib=dylib=pthread"); + println!("cargo:rustc-link-lib=dylib=dl"); + println!("cargo:rustc-link-lib=dylib=m"); + } + } +} + +#[cfg(feature = "regenerate-bindings")] +fn regenerate_bindings(graphblas_install: &Path) { + let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")); + let lagraph_include = manifest_dir.join(LAGRAPH_REL_PATH).join("include"); + assert!( + lagraph_include.join("LAGraph.h").exists(), + "LAGraph.h not found at {}.\n\ + Fetch the submodule:\n git submodule update --init --recursive", + lagraph_include.display() + ); + + let graphblas_include = locate_graphblas_header(graphblas_install); + + let bindings = bindgen::Builder::default() + .header( + lagraph_include + .join("LAGraph.h") + .to_str() + .expect("non-utf8 header path"), + ) + .header( + lagraph_include + .join("LAGraphX.h") + .to_str() + .expect("non-utf8 header path"), + ) + .clang_arg(format!("-I{}", graphblas_include.display())) + .clang_arg(format!("-I{}", lagraph_include.display())) + .allowlist_type("GrB_Index") + .allowlist_type("GrB_Matrix") + .allowlist_type("GrB_Vector") + .allowlist_item("GrB_BOOL") + .allowlist_item("GrB_LOR") + .allowlist_item("GrB_LOR_LAND_SEMIRING_BOOL") + .allowlist_item("GrB_Info") + .allowlist_function("GrB_Matrix_new") + .allowlist_function("GrB_Matrix_nvals") + .allowlist_function("GrB_Matrix_dup") + .allowlist_function("GrB_Matrix_free") + .allowlist_function("GrB_Matrix_extractElement_BOOL") + .allowlist_function("GrB_Matrix_build_BOOL") + .allowlist_function("GrB_Vector_new") + .allowlist_function("GrB_Vector_free") + .allowlist_function("GrB_Vector_setElement_BOOL") + .allowlist_function("GrB_Vector_nvals") + .allowlist_function("GrB_Vector_extractTuples_BOOL") + .allowlist_function("GrB_vxm") + .allowlist_item("LAGRAPH_MSG_LEN") + .allowlist_item("RPQMatrixOp") + .allowlist_type("RPQMatrixPlan") + .allowlist_type("LAGraph_Graph") + .allowlist_type("LAGraph_Kind") + .allowlist_function("LAGraph_CheckGraph") + .allowlist_function("LAGraph_Init") + .allowlist_function("LAGraph_Finalize") + .allowlist_function("LAGraph_SetNumThreads") + .allowlist_function("LAGraph_GetNumThreads") + .allowlist_function("LAGraph_New") + .allowlist_function("LAGraph_Delete") + .allowlist_function("LAGraph_Cached_AT") + .allowlist_function("LAGraph_MMRead") + .allowlist_function("LAGraph_RPQMatrix") + .allowlist_function("LAGraph_RPQMatrix_reduce") + .allowlist_function("LAGraph_DestroyRpqMatrixPlan") + .allowlist_function("LAGraph_RPQMatrix_label") + .allowlist_function("LAGraph_RPQMatrix_Free") + .allowlist_function("LAGraph_RegularPathQuery") + .default_enum_style(bindgen::EnumVariation::Rust { + non_exhaustive: false, + }) + .derive_debug(true) + .derive_copy(true) + .layout_tests(false) + .generate_comments(false) + .generate() + .expect("bindgen failed to generate bindings"); + + bindings + .write_to_file(manifest_dir.join("src/lagraph_sys_generated.rs")) + .expect("failed to write bindgen output to src/lagraph_sys_generated.rs"); +} + +#[cfg(feature = "regenerate-bindings")] +fn locate_graphblas_header(install_prefix: &Path) -> PathBuf { + let candidates = [ + install_prefix.join("include").join("suitesparse"), + install_prefix.join("include"), + ]; + candidates + .iter() + .find(|p| p.join("GraphBLAS.h").exists()) + .cloned() + .unwrap_or_else(|| { + panic!( + "GraphBLAS.h not found in any of {candidates:?}.\n\ + Did the GraphBLAS cmake install step run?" + ) + }) +} diff --git a/deps/LAGraph b/pathrex-sys/deps/LAGraph similarity index 100% rename from deps/LAGraph rename to pathrex-sys/deps/LAGraph diff --git a/src/lagraph_sys_generated.rs b/pathrex-sys/src/lagraph_sys_generated.rs similarity index 100% rename from src/lagraph_sys_generated.rs rename to pathrex-sys/src/lagraph_sys_generated.rs diff --git a/pathrex-sys/src/lib.rs b/pathrex-sys/src/lib.rs new file mode 100644 index 0000000..19cacfa --- /dev/null +++ b/pathrex-sys/src/lib.rs @@ -0,0 +1,80 @@ +//! Raw FFI bindings for SuiteSparse:GraphBLAS and LAGraph. +//! +//! This crate is a `*-sys` crate in the Rust ecosystem sense: it owns the +//! native build (link directives in `build.rs`) and exposes the bindgen- +//! generated symbols verbatim. Higher-level safe wrappers, RAII guards, and +//! Rust APIs live in the `pathrex` crate. +//! +//! Bindings are generated from the LAGraph headers in `deps/LAGraph/include` +//! and the system-installed `GraphBLAS.h`. The generated file +//! `src/lagraph_sys_generated.rs` is checked in; regenerate it with +//! `cargo build --features regenerate-bindings` (requires `libclang`). + +#![allow( + non_camel_case_types, + non_snake_case, + non_upper_case_globals, + dead_code, + clippy::all +)] + +include!("lagraph_sys_generated.rs"); + +use core::fmt; + +impl fmt::Display for GrB_Info { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + GrB_Info::GrB_SUCCESS => write!(f, "GrB_SUCCESS"), + GrB_Info::GrB_NO_VALUE => write!(f, "GrB_NO_VALUE"), + GrB_Info::GxB_EXHAUSTED => write!(f, "GxB_EXHAUSTED"), + GrB_Info::GrB_UNINITIALIZED_OBJECT => write!(f, "GrB_UNINITIALIZED_OBJECT"), + GrB_Info::GrB_NULL_POINTER => write!(f, "GrB_NULL_POINTER"), + GrB_Info::GrB_INVALID_VALUE => write!(f, "GrB_INVALID_VALUE"), + GrB_Info::GrB_INVALID_INDEX => write!(f, "GrB_INVALID_INDEX"), + GrB_Info::GrB_DOMAIN_MISMATCH => write!(f, "GrB_DOMAIN_MISMATCH"), + GrB_Info::GrB_DIMENSION_MISMATCH => write!(f, "GrB_DIMENSION_MISMATCH"), + GrB_Info::GrB_OUTPUT_NOT_EMPTY => write!(f, "GrB_OUTPUT_NOT_EMPTY"), + GrB_Info::GrB_NOT_IMPLEMENTED => write!(f, "GrB_NOT_IMPLEMENTED"), + GrB_Info::GrB_ALREADY_SET => write!(f, "GrB_ALREADY_SET"), + GrB_Info::GrB_PANIC => write!(f, "GrB_PANIC"), + GrB_Info::GrB_OUT_OF_MEMORY => write!(f, "GrB_OUT_OF_MEMORY"), + GrB_Info::GrB_INSUFFICIENT_SPACE => write!(f, "GrB_INSUFFICIENT_SPACE"), + GrB_Info::GrB_INVALID_OBJECT => write!(f, "GrB_INVALID_OBJECT"), + GrB_Info::GrB_INDEX_OUT_OF_BOUNDS => write!(f, "GrB_INDEX_OUT_OF_BOUNDS"), + GrB_Info::GrB_EMPTY_OBJECT => write!(f, "GrB_EMPTY_OBJECT"), + GrB_Info::GxB_JIT_ERROR => write!(f, "GxB_JIT_ERROR"), + GrB_Info::GxB_GPU_ERROR => write!(f, "GxB_GPU_ERROR"), + GrB_Info::GxB_OUTPUT_IS_READONLY => write!(f, "GxB_OUTPUT_IS_READONLY"), + } + } +} + +impl From for GrB_Info { + fn from(value: i32) -> Self { + match value { + 0 => GrB_Info::GrB_SUCCESS, + 1 => GrB_Info::GrB_NO_VALUE, + 7 => GrB_Info::GxB_EXHAUSTED, + -1 => GrB_Info::GrB_UNINITIALIZED_OBJECT, + -2 => GrB_Info::GrB_NULL_POINTER, + -3 => GrB_Info::GrB_INVALID_VALUE, + -4 => GrB_Info::GrB_INVALID_INDEX, + -5 => GrB_Info::GrB_DOMAIN_MISMATCH, + -6 => GrB_Info::GrB_DIMENSION_MISMATCH, + -7 => GrB_Info::GrB_OUTPUT_NOT_EMPTY, + -8 => GrB_Info::GrB_NOT_IMPLEMENTED, + -9 => GrB_Info::GrB_ALREADY_SET, + -101 => GrB_Info::GrB_PANIC, + -102 => GrB_Info::GrB_OUT_OF_MEMORY, + -103 => GrB_Info::GrB_INSUFFICIENT_SPACE, + -104 => GrB_Info::GrB_INVALID_OBJECT, + -105 => GrB_Info::GrB_INDEX_OUT_OF_BOUNDS, + -106 => GrB_Info::GrB_EMPTY_OBJECT, + -7001 => GrB_Info::GxB_JIT_ERROR, + -7002 => GrB_Info::GxB_GPU_ERROR, + -7003 => GrB_Info::GxB_OUTPUT_IS_READONLY, + _ => unimplemented!("Hope no more GrB status codes!"), + } + } +} diff --git a/pathrex/Cargo.toml b/pathrex/Cargo.toml new file mode 100644 index 0000000..6401f1e --- /dev/null +++ b/pathrex/Cargo.toml @@ -0,0 +1,55 @@ +[package] +name = "pathrex" +version = "0.1.0" +edition.workspace = true +rust-version.workspace = true +license.workspace = true +repository.workspace = true +homepage.workspace = true +authors.workspace = true +description = "Library and CLI for benchmarking RPQ/CFL queries on edge-labeled graphs via SuiteSparse:GraphBLAS and LAGraph." +documentation = "https://docs.rs/pathrex" +readme = "../README.md" +categories = ["algorithms", "science", "database", "command-line-utilities"] +keywords = ["graph", "sparql", "rpq", "graphblas", "benchmark"] +include = [ + "Cargo.toml", + "../README.md", + "src/**/*.rs", +] + +[dependencies] +pathrex-sys = { version = "0.1", path = "../pathrex-sys" } + +csv = "1.4.0" +egg = "0.10.0" +libc = "0.2" +memmap2 = "0.9" +oxrdf = "0.3.3" +oxttl = "0.2.3" +rayon = "1" +rustfst = "1.2" +spargebra = "0.4.6" +thiserror = "1.0" + +clap = { version = "4", features = ["derive"], optional = true } +serde = { version = "1", features = ["derive"], optional = true } +serde_json = { version = "1", optional = true } +chrono = { version = "0.4", features = ["serde"], optional = true } +criterion = { version = "0.5", optional = true } +tempfile = { version = "3", optional = true } + +[features] +default = [] +bench = ["clap", "serde", "serde_json", "chrono", "criterion", "tempfile"] + +[dev-dependencies] +tempfile = "3" + +[[bin]] +name = "pathrex" +path = "src/bin/pathrex.rs" +required-features = ["bench"] + +[package.metadata.docs.rs] +features = ["bench"] diff --git a/src/bin/pathrex.rs b/pathrex/src/bin/pathrex.rs similarity index 100% rename from src/bin/pathrex.rs rename to pathrex/src/bin/pathrex.rs diff --git a/src/cli/args.rs b/pathrex/src/cli/args.rs similarity index 100% rename from src/cli/args.rs rename to pathrex/src/cli/args.rs diff --git a/src/cli/bench/error.rs b/pathrex/src/cli/bench/error.rs similarity index 100% rename from src/cli/bench/error.rs rename to pathrex/src/cli/bench/error.rs diff --git a/src/cli/bench/estimates.rs b/pathrex/src/cli/bench/estimates.rs similarity index 100% rename from src/cli/bench/estimates.rs rename to pathrex/src/cli/bench/estimates.rs diff --git a/src/cli/bench/mod.rs b/pathrex/src/cli/bench/mod.rs similarity index 100% rename from src/cli/bench/mod.rs rename to pathrex/src/cli/bench/mod.rs diff --git a/src/cli/bench/runner.rs b/pathrex/src/cli/bench/runner.rs similarity index 100% rename from src/cli/bench/runner.rs rename to pathrex/src/cli/bench/runner.rs diff --git a/src/cli/checkpoint.rs b/pathrex/src/cli/checkpoint.rs similarity index 100% rename from src/cli/checkpoint.rs rename to pathrex/src/cli/checkpoint.rs diff --git a/src/cli/dispatch.rs b/pathrex/src/cli/dispatch.rs similarity index 100% rename from src/cli/dispatch.rs rename to pathrex/src/cli/dispatch.rs diff --git a/src/cli/loader.rs b/pathrex/src/cli/loader.rs similarity index 100% rename from src/cli/loader.rs rename to pathrex/src/cli/loader.rs diff --git a/src/cli/mod.rs b/pathrex/src/cli/mod.rs similarity index 100% rename from src/cli/mod.rs rename to pathrex/src/cli/mod.rs diff --git a/src/cli/output.rs b/pathrex/src/cli/output.rs similarity index 100% rename from src/cli/output.rs rename to pathrex/src/cli/output.rs diff --git a/src/cli/query.rs b/pathrex/src/cli/query.rs similarity index 100% rename from src/cli/query.rs rename to pathrex/src/cli/query.rs diff --git a/src/eval/mod.rs b/pathrex/src/eval/mod.rs similarity index 100% rename from src/eval/mod.rs rename to pathrex/src/eval/mod.rs diff --git a/src/formats/csv.rs b/pathrex/src/formats/csv.rs similarity index 100% rename from src/formats/csv.rs rename to pathrex/src/formats/csv.rs diff --git a/src/formats/mm.rs b/pathrex/src/formats/mm.rs similarity index 100% rename from src/formats/mm.rs rename to pathrex/src/formats/mm.rs diff --git a/src/formats/mod.rs b/pathrex/src/formats/mod.rs similarity index 100% rename from src/formats/mod.rs rename to pathrex/src/formats/mod.rs diff --git a/src/formats/rdf.rs b/pathrex/src/formats/rdf.rs similarity index 100% rename from src/formats/rdf.rs rename to pathrex/src/formats/rdf.rs diff --git a/src/graph/inmemory.rs b/pathrex/src/graph/inmemory.rs similarity index 100% rename from src/graph/inmemory.rs rename to pathrex/src/graph/inmemory.rs diff --git a/src/graph/mod.rs b/pathrex/src/graph/mod.rs similarity index 100% rename from src/graph/mod.rs rename to pathrex/src/graph/mod.rs diff --git a/src/graph/wrappers.rs b/pathrex/src/graph/wrappers.rs similarity index 100% rename from src/graph/wrappers.rs rename to pathrex/src/graph/wrappers.rs diff --git a/pathrex/src/lib.rs b/pathrex/src/lib.rs new file mode 100644 index 0000000..a622330 --- /dev/null +++ b/pathrex/src/lib.rs @@ -0,0 +1,18 @@ +pub mod eval; +pub mod formats; +pub mod graph; +pub mod rpq; +pub mod sparql; +#[allow(unused_unsafe, dead_code)] +pub mod utils; + +/// Re-export of the [`pathrex_sys`] FFI crate under the historical name. +/// +/// Internal modules and integration tests reach the raw GraphBLAS / LAGraph +/// bindings through `crate::lagraph_sys` (and `pathrex::lagraph_sys` from +/// outside the crate). The bindings themselves now live in the dedicated +/// `pathrex-sys` crate; this re-export keeps existing call sites working. +pub use pathrex_sys as lagraph_sys; + +#[cfg(feature = "bench")] +pub mod cli; diff --git a/src/rpq/mod.rs b/pathrex/src/rpq/mod.rs similarity index 100% rename from src/rpq/mod.rs rename to pathrex/src/rpq/mod.rs diff --git a/src/rpq/nfarpq.rs b/pathrex/src/rpq/nfarpq.rs similarity index 100% rename from src/rpq/nfarpq.rs rename to pathrex/src/rpq/nfarpq.rs diff --git a/src/rpq/rpqmatrix.rs b/pathrex/src/rpq/rpqmatrix.rs similarity index 100% rename from src/rpq/rpqmatrix.rs rename to pathrex/src/rpq/rpqmatrix.rs diff --git a/src/sparql/mod.rs b/pathrex/src/sparql/mod.rs similarity index 100% rename from src/sparql/mod.rs rename to pathrex/src/sparql/mod.rs diff --git a/src/utils.rs b/pathrex/src/utils.rs similarity index 58% rename from src/utils.rs rename to pathrex/src/utils.rs index edf2b18..30477fb 100644 --- a/src/utils.rs +++ b/pathrex/src/utils.rs @@ -1,5 +1,5 @@ -use crate::{graph::*, lagraph_sys::*}; -use std::{fmt::Display, sync::Arc}; +use crate::graph::*; +use std::sync::Arc; pub struct CountOutput(pub usize, std::marker::PhantomData); @@ -65,63 +65,6 @@ impl GraphSource) -> std::fmt::Result { - match self { - GrB_Info::GrB_SUCCESS => write!(f, "GrB_SUCCESS"), - GrB_Info::GrB_NO_VALUE => write!(f, "GrB_NO_VALUE"), - GrB_Info::GxB_EXHAUSTED => write!(f, "GxB_EXHAUSTED"), - GrB_Info::GrB_UNINITIALIZED_OBJECT => write!(f, "GrB_UNINITIALIZED_OBJECT"), - GrB_Info::GrB_NULL_POINTER => write!(f, "GrB_NULL_POINTER"), - GrB_Info::GrB_INVALID_VALUE => write!(f, "GrB_INVALID_VALUE"), - GrB_Info::GrB_INVALID_INDEX => write!(f, "GrB_INVALID_INDEX"), - GrB_Info::GrB_DOMAIN_MISMATCH => write!(f, "GrB_DOMAIN_MISMATCH"), - GrB_Info::GrB_DIMENSION_MISMATCH => write!(f, "GrB_DIMENSION_MISMATCH"), - GrB_Info::GrB_OUTPUT_NOT_EMPTY => write!(f, "GrB_OUTPUT_NOT_EMPTY"), - GrB_Info::GrB_NOT_IMPLEMENTED => write!(f, "GrB_NOT_IMPLEMENTED"), - GrB_Info::GrB_ALREADY_SET => write!(f, "GrB_ALREADY_SET"), - GrB_Info::GrB_PANIC => write!(f, "GrB_PANIC"), - GrB_Info::GrB_OUT_OF_MEMORY => write!(f, "GrB_OUT_OF_MEMORY"), - GrB_Info::GrB_INSUFFICIENT_SPACE => write!(f, "GrB_INSUFFICIENT_SPACE"), - GrB_Info::GrB_INVALID_OBJECT => write!(f, "GrB_INVALID_OBJECT"), - GrB_Info::GrB_INDEX_OUT_OF_BOUNDS => write!(f, "GrB_INDEX_OUT_OF_BOUNDS"), - GrB_Info::GrB_EMPTY_OBJECT => write!(f, "GrB_EMPTY_OBJECT"), - GrB_Info::GxB_JIT_ERROR => write!(f, "GxB_JIT_ERROR"), - GrB_Info::GxB_GPU_ERROR => write!(f, "GxB_GPU_ERROR"), - GrB_Info::GxB_OUTPUT_IS_READONLY => write!(f, "GxB_OUTPUT_IS_READONLY"), - } - } -} - -impl From for GrB_Info { - fn from(value: i32) -> Self { - match value { - 0 => GrB_Info::GrB_SUCCESS, - 1 => GrB_Info::GrB_NO_VALUE, - 7 => GrB_Info::GxB_EXHAUSTED, - -1 => GrB_Info::GrB_UNINITIALIZED_OBJECT, - -2 => GrB_Info::GrB_NULL_POINTER, - -3 => GrB_Info::GrB_INVALID_VALUE, - -4 => GrB_Info::GrB_INVALID_INDEX, - -5 => GrB_Info::GrB_DOMAIN_MISMATCH, - -6 => GrB_Info::GrB_DIMENSION_MISMATCH, - -7 => GrB_Info::GrB_OUTPUT_NOT_EMPTY, - -8 => GrB_Info::GrB_NOT_IMPLEMENTED, - -9 => GrB_Info::GrB_ALREADY_SET, - -101 => GrB_Info::GrB_PANIC, - -102 => GrB_Info::GrB_OUT_OF_MEMORY, - -103 => GrB_Info::GrB_INSUFFICIENT_SPACE, - -104 => GrB_Info::GrB_INVALID_OBJECT, - -105 => GrB_Info::GrB_INDEX_OUT_OF_BOUNDS, - -106 => GrB_Info::GrB_EMPTY_OBJECT, - -7001 => GrB_Info::GxB_JIT_ERROR, - -7002 => GrB_Info::GxB_GPU_ERROR, - -7003 => GrB_Info::GxB_OUTPUT_IS_READONLY, - _ => unimplemented!("Hope no more GrB status codes!"), - } - } -} - /// Calls a raw GraphBLAS function expression and maps its `i32` return code to /// `Result<(), GraphError>`. /// diff --git a/tests/inmemory_tests.rs b/pathrex/tests/inmemory_tests.rs similarity index 100% rename from tests/inmemory_tests.rs rename to pathrex/tests/inmemory_tests.rs diff --git a/tests/mm_tests.rs b/pathrex/tests/mm_tests.rs similarity index 100% rename from tests/mm_tests.rs rename to pathrex/tests/mm_tests.rs diff --git a/tests/nfarpq_tests.rs b/pathrex/tests/nfarpq_tests.rs similarity index 100% rename from tests/nfarpq_tests.rs rename to pathrex/tests/nfarpq_tests.rs diff --git a/tests/rpqmatrix_tests.rs b/pathrex/tests/rpqmatrix_tests.rs similarity index 100% rename from tests/rpqmatrix_tests.rs rename to pathrex/tests/rpqmatrix_tests.rs diff --git a/tests/testdata/cases/any-any/expected.txt b/pathrex/tests/testdata/cases/any-any/expected.txt similarity index 100% rename from tests/testdata/cases/any-any/expected.txt rename to pathrex/tests/testdata/cases/any-any/expected.txt diff --git a/tests/testdata/cases/any-any/queries.txt b/pathrex/tests/testdata/cases/any-any/queries.txt similarity index 100% rename from tests/testdata/cases/any-any/queries.txt rename to pathrex/tests/testdata/cases/any-any/queries.txt diff --git a/tests/testdata/cases/any-con/expected.txt b/pathrex/tests/testdata/cases/any-con/expected.txt similarity index 100% rename from tests/testdata/cases/any-con/expected.txt rename to pathrex/tests/testdata/cases/any-con/expected.txt diff --git a/tests/testdata/cases/any-con/queries.txt b/pathrex/tests/testdata/cases/any-con/queries.txt similarity index 100% rename from tests/testdata/cases/any-con/queries.txt rename to pathrex/tests/testdata/cases/any-con/queries.txt diff --git a/tests/testdata/cases/con-any/expected.txt b/pathrex/tests/testdata/cases/con-any/expected.txt similarity index 100% rename from tests/testdata/cases/con-any/expected.txt rename to pathrex/tests/testdata/cases/con-any/expected.txt diff --git a/tests/testdata/cases/con-any/queries.txt b/pathrex/tests/testdata/cases/con-any/queries.txt similarity index 100% rename from tests/testdata/cases/con-any/queries.txt rename to pathrex/tests/testdata/cases/con-any/queries.txt diff --git a/tests/testdata/mm_graph/1.txt b/pathrex/tests/testdata/mm_graph/1.txt similarity index 100% rename from tests/testdata/mm_graph/1.txt rename to pathrex/tests/testdata/mm_graph/1.txt diff --git a/tests/testdata/mm_graph/2.txt b/pathrex/tests/testdata/mm_graph/2.txt similarity index 100% rename from tests/testdata/mm_graph/2.txt rename to pathrex/tests/testdata/mm_graph/2.txt diff --git a/tests/testdata/mm_graph/3.txt b/pathrex/tests/testdata/mm_graph/3.txt similarity index 100% rename from tests/testdata/mm_graph/3.txt rename to pathrex/tests/testdata/mm_graph/3.txt diff --git a/tests/testdata/mm_graph/4.txt b/pathrex/tests/testdata/mm_graph/4.txt similarity index 100% rename from tests/testdata/mm_graph/4.txt rename to pathrex/tests/testdata/mm_graph/4.txt diff --git a/tests/testdata/mm_graph/5.txt b/pathrex/tests/testdata/mm_graph/5.txt similarity index 100% rename from tests/testdata/mm_graph/5.txt rename to pathrex/tests/testdata/mm_graph/5.txt diff --git a/tests/testdata/mm_graph/6.txt b/pathrex/tests/testdata/mm_graph/6.txt similarity index 100% rename from tests/testdata/mm_graph/6.txt rename to pathrex/tests/testdata/mm_graph/6.txt diff --git a/tests/testdata/mm_graph/7.txt b/pathrex/tests/testdata/mm_graph/7.txt similarity index 100% rename from tests/testdata/mm_graph/7.txt rename to pathrex/tests/testdata/mm_graph/7.txt diff --git a/tests/testdata/mm_graph/8.txt b/pathrex/tests/testdata/mm_graph/8.txt similarity index 100% rename from tests/testdata/mm_graph/8.txt rename to pathrex/tests/testdata/mm_graph/8.txt diff --git a/tests/testdata/mm_graph/9.txt b/pathrex/tests/testdata/mm_graph/9.txt similarity index 100% rename from tests/testdata/mm_graph/9.txt rename to pathrex/tests/testdata/mm_graph/9.txt diff --git a/tests/testdata/mm_graph/edges.txt b/pathrex/tests/testdata/mm_graph/edges.txt similarity index 100% rename from tests/testdata/mm_graph/edges.txt rename to pathrex/tests/testdata/mm_graph/edges.txt diff --git a/tests/testdata/mm_graph/vertices.txt b/pathrex/tests/testdata/mm_graph/vertices.txt similarity index 100% rename from tests/testdata/mm_graph/vertices.txt rename to pathrex/tests/testdata/mm_graph/vertices.txt diff --git a/src/lagraph_sys.rs b/src/lagraph_sys.rs deleted file mode 100644 index 055a0c6..0000000 --- a/src/lagraph_sys.rs +++ /dev/null @@ -1,10 +0,0 @@ -//! FFI bindings for SuiteSparse:GraphBLAS and LAGraph. -#![allow( - non_camel_case_types, - non_snake_case, - non_upper_case_globals, - dead_code, - clippy::all -)] - -include!("lagraph_sys_generated.rs"); diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 2502767..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,12 +0,0 @@ -pub mod eval; -pub mod formats; -pub mod graph; -pub mod rpq; -pub mod sparql; -#[allow(unused_unsafe, dead_code)] -pub mod utils; - -pub mod lagraph_sys; - -#[cfg(feature = "bench")] -pub mod cli; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index e7a11a9..0000000 --- a/src/main.rs +++ /dev/null @@ -1,3 +0,0 @@ -fn main() { - println!("Hello, world!"); -}