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
35 changes: 20 additions & 15 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,34 +7,25 @@ on:

env:
CARGO_TERM_COLOR: always
RAYFORCE_SRC: ${{ github.workspace }}/rayforce-core
RAYFORCE_Q_SRC: ${{ github.workspace }}/rayforce-q

jobs:
test:
runs-on: ubuntu-latest
steps:
# The C core and the rayforce-q client are submodules under
# rayforce-sys/vendor/, so this one checkout brings the whole build.
- name: Checkout bindings
uses: actions/checkout@v4

- name: Checkout RayforceDB core
uses: actions/checkout@v4
with:
repository: RayforceDB/rayforce
path: rayforce-core
submodules: recursive

- name: Checkout rayforce-q client
uses: actions/checkout@v4
with:
repository: RayforceDB/rayforce-q
path: rayforce-q
- name: Check vendored core pin
run: ./scripts/check-vendored-pin.sh

- name: Install toolchain deps
run: sudo apt-get update && sudo apt-get install -y clang libclang-dev build-essential

# The repo's .cargo/config.toml sets a macOS LIBCLANG_PATH for local dev.
# On Linux that path is invalid, so point bindgen at the apt libclang here
# — an env var set in the job takes precedence over the config default.
# bindgen needs to be told where libclang lives on the runner.
- name: Locate libclang
run: |
LIB="$(find /usr/lib -name 'libclang*.so*' 2>/dev/null | head -1)"
Expand Down Expand Up @@ -64,3 +55,17 @@ jobs:

- name: Test
run: cargo test --workspace

# The vendored sources must actually land in the .crate — that is the
# whole reason docs.rs and other network-isolated builds work. Cheap to
# check here, and a broken package is invisible until someone consumes it.
- name: Check the packaged crate carries the vendored core
run: |
cargo package -p rayforce-sys --list --allow-dirty > /tmp/pkg.txt
for f in vendor/rayforce/include/rayforce.h vendor/rayforce/Makefile vendor/rayforce-q/q.c; do
grep -qx "$f" /tmp/pkg.txt || { echo "::error::$f missing from the packaged crate"; exit 1; }
done
if grep -qE '\.(o|d)$' /tmp/pkg.txt; then
echo "::error::build artifacts leaked into the packaged crate"; exit 1
fi
echo "packaged $(wc -l < /tmp/pkg.txt) files"
40 changes: 20 additions & 20 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ on:

env:
CARGO_TERM_COLOR: always
# The publish verify build (and the downstream rayforce-sys build triggered
# when publishing `rayforce`) reads these to avoid re-cloning the C sources.
RAYFORCE_SRC: ${{ github.workspace }}/rayforce-core
RAYFORCE_Q_SRC: ${{ github.workspace }}/rayforce-q

jobs:
publish:
Expand All @@ -24,30 +20,21 @@ jobs:
id-token: write
contents: read
steps:
# The C sources are submodules under rayforce-sys/vendor/, so what is
# built and published here is exactly what a crates.io consumer gets —
# no separate checkout to keep in step with build.rs.
- name: Checkout bindings
uses: actions/checkout@v4

# Pin the same refs as rayforce-sys/build.rs so a source build here
# matches what a crates.io consumer gets.
- name: Checkout RayforceDB core (v2.5.1)
uses: actions/checkout@v4
with:
repository: RayforceDB/rayforce
ref: 'v2.5.1'
path: rayforce-core
submodules: recursive

- name: Checkout rayforce-q client (2.0.0)
uses: actions/checkout@v4
with:
repository: RayforceDB/rayforce-q
ref: '2.0.0'
path: rayforce-q
- name: Check vendored core pin
run: ./scripts/check-vendored-pin.sh

- name: Install toolchain deps
run: sudo apt-get update && sudo apt-get install -y clang libclang-dev build-essential

# .cargo/config.toml sets a macOS LIBCLANG_PATH for local dev; on Linux
# that path is invalid, so point bindgen at the apt libclang here.
# bindgen needs to be told where libclang lives on the runner.
- name: Locate libclang
run: |
LIB="$(find /usr/lib -name 'libclang*.so*' 2>/dev/null | head -1)"
Expand All @@ -70,6 +57,19 @@ jobs:
- name: Test workspace
run: cargo test --workspace

# A .crate missing its vendored sources would build fine in this job (the
# submodule is on disk) and then fail for every consumer and on docs.rs.
# This is the last point at which that is catchable.
- name: Check the packaged crate carries the vendored core
run: |
cargo package -p rayforce-sys --list > /tmp/pkg.txt
for f in vendor/rayforce/include/rayforce.h vendor/rayforce/Makefile vendor/rayforce-q/q.c; do
grep -qx "$f" /tmp/pkg.txt || { echo "::error::$f missing from the packaged crate"; exit 1; }
done
if grep -qE '\.(o|d)$' /tmp/pkg.txt; then
echo "::error::build artifacts leaked into the packaged crate"; exit 1
fi

# Trusted Publishing: exchanges the job's OIDC identity for a short-lived
# crates.io token. No CARGO_REGISTRY_TOKEN secret needed — but both
# `rayforce` and `rayforce-sys` must have this repo/workflow registered as
Expand Down
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
[submodule "rayforce-sys/vendor/rayforce"]
path = rayforce-sys/vendor/rayforce
url = https://github.com/RayforceDB/rayforce.git
[submodule "rayforce-sys/vendor/rayforce-q"]
path = rayforce-sys/vendor/rayforce-q
url = https://github.com/RayforceDB/rayforce-q.git
51 changes: 43 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ println!("{result}");

## Installation

The crate links two local checkouts: the RayforceDB **core** (`RAYFORCE_SRC`) and the
**rayforce-q** IPC client (`RAYFORCE_Q_SRC`). The build script compiles both and statically
The RayforceDB **core** and the **rayforce-q** IPC client are C. Both ship inside the
crate, so nothing is fetched at build time — the build script compiles them and statically
links `librayforce.a`.

```toml
Expand All @@ -98,18 +98,53 @@ links `librayforce.a`.
rayforce = { git = "https://github.com/RayforceDB/rayforce-rs" }
```

```sh
git clone https://github.com/RayforceDB/rayforce ~/rayforce # core
git clone https://github.com/RayforceDB/rayforce-q ~/rayforce-q # Q IPC client
Requirements: a C toolchain (`make`, `clang`) and `libclang` for `bindgen`.

### Working on the bindings

export RAYFORCE_SRC=/path/to/rayforce # default: ~/rayforce
export RAYFORCE_Q_SRC=/path/to/rayforce-q # default: ~/rayforce-q
The C sources live in git submodules under `rayforce-sys/vendor/`, so a checkout needs
them initialized:

```sh
git clone --recurse-submodules https://github.com/RayforceDB/rayforce-rs
# in an existing clone:
git submodule update --init --recursive

cargo build
cargo test
```

Requirements: a C toolchain (`make`, `clang`) and `libclang` for `bindgen`.
### Choosing the core version

Each release links one pinned core version. It lives in two places that must agree — the
`rayforce-sys/vendor/rayforce` submodule, and the `CORE_VERSION` / `CORE_COMMIT` constants
in `rayforce-sys/build.rs` that get stamped into `librayforce.a` (a crate unpacked from
crates.io has no git history for the core's Makefile to read a version from).

To move the pin, move both:

```sh
git -C rayforce-sys/vendor/rayforce fetch --tags
git -C rayforce-sys/vendor/rayforce checkout v2.6.0
git add rayforce-sys/vendor/rayforce

git -C rayforce-sys/vendor/rayforce rev-parse --short=7 HEAD # CORE_COMMIT
$EDITOR rayforce-sys/build.rs # CORE_VERSION, CORE_COMMIT

./scripts/check-vendored-pin.sh # names the mismatch if they disagree
cargo test --workspace
```

`rayforce-sys/vendor/rayforce-q` works the same way, minus the constants — nothing is
stamped from it.

To build against a core you are changing instead, point the build script at your own
checkout. These take precedence over the vendored copies:

```sh
export RAYFORCE_SRC=/path/to/rayforce
export RAYFORCE_Q_SRC=/path/to/rayforce-q
```

`bindgen` locates `libclang` via `LIBCLANG_PATH`. This is deliberately **not** set in the
repo's `.cargo/config.toml`. If bindgen can't auto-detect libclang, set it yourself:
Expand Down
91 changes: 76 additions & 15 deletions docs/docs/content/get-started/installation.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# :octicons-package-16: Installation

`rayforce` builds against a local checkout of the RayforceDB core. The build
script compiles the core into a static library (`librayforce.a`) and statically
links it, so there is nothing to install at runtime.
The RayforceDB core is C, and it ships inside the `rayforce-sys` crate as a
pinned git submodule. The build script compiles it into a static library
(`librayforce.a`) and links it statically — so there is nothing to fetch while
building, and nothing to install at runtime.

## :material-clipboard-check-outline: Prerequisites

Expand All @@ -11,7 +12,6 @@ links it, so there is nothing to install at runtime.
- A **C toolchain** — `make` and `clang` — to build the RayforceDB core.
- **`libclang`**, required by [`bindgen`](https://github.com/rust-lang/rust-bindgen)
to generate the raw FFI bindings.
- A **RayforceDB core** checkout to link against (see below).

!!! note "macOS: `LIBCLANG_PATH`"
On macOS `bindgen` may not find `libclang` automatically. Point it at your
Expand All @@ -24,23 +24,84 @@ links it, so there is nothing to install at runtime.
The repository's `.cargo/config.toml` is the place to set this permanently
for local builds.

## :material-source-branch: Building against a local core
## :material-tag-outline: Which core version gets linked

The build links a local RayforceDB core checkout. Point the `RAYFORCE_SRC`
environment variable at it; it defaults to `~/rayforce`. The build script runs
the core's `make lib` to produce `librayforce.a`, then links it.
Each release of `rayforce` links one specific core version. It is pinned in two
places that must agree:

```sh
git clone https://github.com/RayforceDB/rayforce-rs.git ~/rayforce
| What | Where |
| --- | --- |
| The core sources | the `rayforce-sys/vendor/rayforce` submodule |
| The version stamped into the library | `CORE_VERSION` / `CORE_COMMIT` in `rayforce-sys/build.rs` |

The constants exist because the core's `Makefile` normally resolves its version
from `git describe`, and a crate unpacked from crates.io has no git history to
read. `scripts/check-vendored-pin.sh` asserts the two agree, and CI runs it on
every push.

As a consumer you get the core that matches the `rayforce` version you depend
on — pick a different core by picking a different `rayforce` release. The two
sections below are for changing that pin yourself.

# Point the build at it (default is ~/rayforce, so this is optional there).
export RAYFORCE_SRC=~/rayforce
### :material-source-branch: Building against your own core checkout

To develop against a core you are changing, point `RAYFORCE_SRC` at it. It takes
precedence over the vendored copy, and is built in place so your incremental
state and the version its git history reports are preserved. `RAYFORCE_Q_SRC`
does the same for the `rayforce-q` IPC client.

```sh
export RAYFORCE_SRC=/path/to/rayforce
export RAYFORCE_Q_SRC=/path/to/rayforce-q

# Build and test the bindings.
cargo build
cargo test
```

Unset them to go back to the vendored sources.

### :material-arrow-up-bold-box-outline: Bumping the pinned version

Moving the pin means moving the submodule and the constants together:

```sh
# 1. Move the submodule to the new tag.
git -C rayforce-sys/vendor/rayforce fetch --tags
git -C rayforce-sys/vendor/rayforce checkout v2.6.0
git add rayforce-sys/vendor/rayforce

# 2. Read back the values build.rs must stamp.
git -C rayforce-sys/vendor/rayforce describe --tags --exact-match # -> v2.6.0
git -C rayforce-sys/vendor/rayforce rev-parse --short=7 HEAD # -> e.g. 1a2b3c4
```

Then edit `rayforce-sys/build.rs` to match — `CORE_VERSION` is the tag without
its leading `v`:

```rust
const CORE_VERSION: &str = "2.6.0";
const CORE_COMMIT: &str = "1a2b3c4";
```

And check the result:

```sh
./scripts/check-vendored-pin.sh # fails, with the mismatch named, if they disagree
cargo test --workspace
```

The same applies to `rayforce-sys/vendor/rayforce-q`, minus the constants —
nothing is stamped from it, so moving the submodule is the whole change.

!!! warning "A new core may need the bindgen allowlist updated"
A few of the symbols the safe crate calls are not in the public
`rayforce.h` — they are read from the core's private headers instead.
`CORE_PRIVATE_HEADERS` and `INTERNAL_FNS` in `rayforce-sys/build.rs` name
those headers and symbols. Signatures need no maintenance, since bindgen
reads them from the core, but a bump that renames or relocates one will
fail the build: bindgen emits nothing for it and the safe crate stops
compiling against `rayforce_sys`. Fix it by updating those two lists.

!!! note "Tests run single-threaded"
The engine runs on a single thread with one live runtime per process, so the
test suite is serialized. Run it with `RUST_TEST_THREADS=1` (or via the
Expand All @@ -58,7 +119,7 @@ or in `Cargo.toml`:

```toml
[dependencies]
rayforce = "0.1"
rayforce = "1"
```

### The `chrono` feature (default)
Expand All @@ -72,7 +133,7 @@ To build without it, disable default features:

```toml
[dependencies]
rayforce = { version = "0.1", default-features = false }
rayforce = { version = "1", default-features = false }
```

## :material-arrow-right: Next steps
Expand Down
23 changes: 23 additions & 0 deletions rayforce-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,29 @@ rust-version.workspace = true
links = "rayforce"
build = "build.rs"

# The C sources ship inside the .crate so a build never needs the network
# (docs.rs and other sandboxes have none). Deny-by-default: the vendored repos
# carry test suites and websites that would otherwise bloat the package —
# `make lib` only needs src/*/*.c, include/ and the Makefile.
#
# The extension filters are load-bearing: an `include` list is matched against
# the filesystem, not against git, so a bare `src/**` would package the .o/.d
# artifacts of a local `make lib` despite the core's .gitignore.
include = [
"src/**",
"tests/**",
"build.rs",
"Cargo.toml",
"vendor/rayforce/src/**/*.c",
"vendor/rayforce/src/**/*.h",
"vendor/rayforce/include/*.h",
"vendor/rayforce/Makefile",
"vendor/rayforce/LICENSE",
"vendor/rayforce-q/q.c",
"vendor/rayforce-q/q.h",
"vendor/rayforce-q/LICENSE",
]

[build-dependencies]
bindgen = "0.70"
cc = "1"
Expand Down
Loading
Loading