Skip to content

Commit afaf853

Browse files
authored
Merge pull request #73 from tinyhumansai/crates-layout
Move every crate under crates/ and reach them all by feature
2 parents c027b8b + 1db0388 commit afaf853

431 files changed

Lines changed: 444 additions & 247 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ jobs:
5858
#
5959
# This is also the configuration that keeps the promise the feature
6060
# exists for: no `git2` / `libgit2-sys` in the graph.
61-
# `api/Cargo.toml` spells out this exact command in a comment and asks
61+
# `crates/tinymemory-api/Cargo.toml` spells out this command in a comment and asks
6262
# that the contract crate never link a storage engine, a native library,
6363
# an HTTP client, or an async runtime. It was left as a comment, so
6464
# nothing checked it — and a forbidden dependency arrives transitively,
@@ -84,7 +84,7 @@ jobs:
8484
run: ./scripts/ci/dependency-budget.sh
8585

8686
- name: Run the bundled example
87-
run: cargo run --example basic
87+
run: cargo run -p tinymemory --example basic
8888

8989
- name: Assert engine containment (#18 §C1)
9090
run: ./scripts/ci/engine-containment.sh

.github/workflows/release.yml

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -83,9 +83,17 @@ jobs:
8383
run: |
8484
set -euo pipefail
8585
86+
# The root manifest is a virtual workspace — every crate lives under
87+
# `crates/`, and there is no root package. So name the facade rather
88+
# than taking `.packages[0]`, which is whichever member cargo happened
89+
# to list first and would silently start releasing a different crate's
90+
# version the day that order changes.
8691
metadata="$(cargo metadata --format-version 1 --no-deps)"
87-
crate_name="$(jq -r '.packages[0].name' <<< "$metadata")"
88-
current_version="$(jq -r '.packages[0].version' <<< "$metadata")"
92+
crate_name="tinymemory"
93+
current_version="$(
94+
jq -r --arg name "$crate_name" \
95+
'.packages[] | select(.name == $name) | .version' <<< "$metadata"
96+
)"
8997
if [[ -z "$current_version" || "$current_version" == "null" ]]; then
9098
echo "Could not resolve the current crate version" >&2
9199
exit 1
@@ -127,7 +135,7 @@ jobs:
127135
echo "tag=${tag}"
128136
} >> "$GITHUB_OUTPUT"
129137
130-
# Bumps the root `[package]` version only. That is sufficient *because* no
138+
# Bumps the facade's `[package]` version only. That is sufficient *because* no
131139
# intra-workspace path dependency carries a `version = "…"` requirement —
132140
# a `minor` bump to 0.2.0 against a sibling asking for `^0.1.0` fails
133141
# resolution here with "failed to select a version", which is exactly how
@@ -152,19 +160,20 @@ jobs:
152160
echo "An intra-workspace path dependency carries a version requirement:" >&2
153161
echo "$offenders" >&2
154162
echo >&2
155-
echo "Bumping the root package will fail to resolve against it. Nothing here" >&2
163+
echo "Bumping the facade will fail to resolve against it. Nothing here" >&2
156164
echo "is published to crates.io, so drop the 'version' key and keep 'path'." >&2
157165
exit 1
158166
fi
159167
160-
perl -0pi -e 's/(\[package\][\s\S]*?\nversion = ")[^"]+(")/$1$ENV{NEXT_VERSION}$2/' Cargo.toml
168+
perl -0pi -e 's/(\[package\][\s\S]*?\nversion = ")[^"]+(")/$1$ENV{NEXT_VERSION}$2/' \
169+
crates/tinymemory/Cargo.toml
161170
cargo update -p "$CRATE_NAME" --precise "$NEXT_VERSION"
162171
163172
# There are TWO Cargo worlds here, and the module's is the one the
164173
# release actually builds. `crates/tinymemory-module` is its own
165174
# workspace root with its own `Cargo.lock` (see the root Cargo.toml
166-
# comment for why), and it depends on the root crate by path — so
167-
# bumping the root version leaves that lockfile recording the old one.
175+
# comment for why), and it depends on the facade by path — so bumping
176+
# the facade's version leaves that lockfile recording the old one.
168177
#
169178
# `native-bundles` then builds with `--locked` and every one of the
170179
# eleven jobs fails with "cannot update the lock file … because
@@ -188,7 +197,8 @@ jobs:
188197
# Both lockfiles: the module's own workspace lock is what the bundle
189198
# jobs build against with `--locked`, so a tag that omits it cannot be
190199
# built at all.
191-
git add Cargo.toml Cargo.lock crates/tinymemory-module/Cargo.lock
200+
git add crates/tinymemory/Cargo.toml Cargo.lock \
201+
crates/tinymemory-module/Cargo.lock
192202
git commit -m "Release ${RELEASE_TAG}"
193203
git tag -a "${RELEASE_TAG}" -m "Release ${RELEASE_TAG}"
194204

AGENTS.md

Lines changed: 58 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -9,37 +9,48 @@ no longer applies rather than leaving it to rot.
99

1010
## Project Structure
1111

12-
This is a Cargo **workspace**: the `tinymemory` facade at the root, the
13-
`tinymemory-api` contract in `api/`, and one engine adapter per directory under
14-
`adapters/`. Engines themselves are submodules under `vendor/`, excluded from
15-
the workspace.
16-
17-
See [`README.md`](README.md) for the layout and the rules that govern it — in
18-
particular, why policy stays in the host and why adapters name their engines by
19-
version requirement rather than by path.
12+
This is a Cargo **workspace** with a virtual root: there is no root package,
13+
and every crate lives in its own directory under `crates/`, named for the
14+
package it holds. `members` is the glob `crates/*`, so a new crate joins the
15+
workspace by existing. `crates/tinymemory` is the facade a host depends on;
16+
`crates/tinymemory-api` is the contract; the rest are the subsystems and the
17+
engine adapters, each reachable from the facade by a feature named after it.
18+
Engines themselves are submodules under `vendor/`, excluded from the workspace.
19+
20+
See [`README.md`](README.md) for the full layout, the feature table, and the
21+
rules that govern them — in particular, why policy stays in the host and why
22+
adapters name their engines by version requirement rather than by path.
2023

2124
```text
22-
src/
23-
├── lib.rs # crate docs + the entire public re-export surface
24-
├── error/mod.rs # crate-wide `Error` and `Result<T>`
25-
└── <feature>/ # one directory per feature area
26-
├── mod.rs # module docs, wiring, smallest useful public API
27-
├── types.rs # substantial type definitions
28-
└── test.rs # module-local unit tests
29-
tests/ # integration tests against the public API only
30-
examples/ # runnable, compiled-in-CI usage examples
25+
crates/<package>/
26+
├── Cargo.toml # one package; `[lints]` opted into per crate
27+
├── README.md # required of complex crates: design, surface, caveats
28+
└── src/
29+
├── lib.rs # crate docs + the entire public re-export surface
30+
├── error/mod.rs # crate-wide `Error` and `Result<T>`
31+
└── <feature>/ # one directory per feature area
32+
├── mod.rs # module docs, wiring, smallest useful public API
33+
├── types.rs # substantial type definitions
34+
└── test.rs # module-local unit tests
35+
crates/<package>/tests/ # integration tests against the public API only
36+
crates/<package>/examples/ # runnable, compiled-in-CI usage examples
3137
vendor/tinybus/ # pinned TinyBus source; optional until wired by a project
3238
docs/
3339
├── specs/ # behavior and architecture specifications
3440
├── plans/ # test-first implementation plans
3541
└── adr/ # immutable architecture decision records
3642
```
3743

38-
Each feature area belongs in a focused module directory under `src/`. A module
39-
root explains the module, wires its pieces together, and exposes the smallest
40-
useful API. Move substantial type definitions into `types.rs` and put
41-
module-local unit tests in a dedicated `test.rs`, wired from the bottom of the
42-
module root with:
44+
A new crate goes in `crates/<package>/`, and a package that is not an adapter
45+
or a subsystem of the memory layer probably does not belong here at all. Reach
46+
it from the facade by adding an optional dependency and a feature of the same
47+
name, so a host keeps taking one dependency and stating what it wants.
48+
49+
Each feature area belongs in a focused module directory under the crate's
50+
`src/`. A module root explains the module, wires its pieces together, and
51+
exposes the smallest useful API. Move substantial type definitions into
52+
`types.rs` and put module-local unit tests in a dedicated `test.rs`, wired from
53+
the bottom of the module root with:
4354

4455
```rust
4556
#[cfg(test)]
@@ -51,9 +62,10 @@ let a general-purpose `utils.rs` or `helpers.rs` grow — those are a symptom of
5162
missing module. Prefer many small modules that each do one thing well over few
5263
broad ones.
5364

54-
Keep public exports centralized in `src/lib.rs` so downstream users have one
55-
predictable surface. Put shared error variants in `src/error/mod.rs` and return
56-
the crate-wide `Result<T>` from fallible public APIs.
65+
Keep public exports centralized in each crate's `src/lib.rs` so downstream
66+
users have one predictable surface. Put shared error variants in
67+
`src/error/mod.rs` and return the crate-wide `Result<T>` from fallible public
68+
APIs.
5769

5870
## Build And Test
5971

@@ -71,7 +83,9 @@ Supporting commands:
7183

7284
- `cargo fmt --all` — format before committing.
7385
- `cargo test <filter>` — run a focused subset while iterating.
74-
- `cargo run --example basic` — run the bundled example.
86+
- `cargo run -p tinymemory --example basic` — run the bundled example. The
87+
`-p` is required: the workspace root is virtual, so cargo cannot infer which
88+
package an example belongs to.
7589
- `cargo doc --no-deps --all-features` — build the rustdoc CI also builds with
7690
`RUSTDOCFLAGS="-D warnings"`.
7791
- `cargo test --doc` — run doctests alone when editing documentation examples.
@@ -92,14 +106,16 @@ Use standard `rustfmt` output and Rust 2024 idioms. Do not hand-format around
92106
- Prefer small, typed APIs over stringly-typed ones. Accept `&str` and generic
93107
`impl Into<String>` at boundaries; return owned, concrete types.
94108
- Keep the public surface minimal: default to private, and export deliberately
95-
from `src/lib.rs`.
96-
- `unsafe` is forbidden crate-wide by the lint configuration in `Cargo.toml`.
97-
If a project genuinely needs it, relax the lint in its own commit and document
98-
every invariant with a `// SAFETY:` comment.
109+
from the crate's `src/lib.rs`.
110+
- `unsafe` is forbidden crate-wide by the `[lints]` table in each crate's own
111+
`Cargo.toml` — the root is virtual and carries no lint configuration. If a
112+
crate genuinely needs it, relax the lint in its own commit and document every
113+
invariant with a `// SAFETY:` comment.
99114

100115
### Errors
101116

102-
- One crate-wide `Error` enum in `src/error/mod.rs`, built with `thiserror`.
117+
- One crate-wide `Error` enum in the crate's `src/error/mod.rs`, built with
118+
`thiserror`.
103119
- Fallible public functions return `Result<T>`, the crate alias.
104120
- Add a specific variant instead of stuffing context into a string; error
105121
messages are lowercase, without trailing punctuation.
@@ -143,10 +159,10 @@ on every generated crate.
143159

144160
## Testing
145161

146-
- Module-local unit tests live in `src/<feature>/test.rs` and may touch private
147-
items.
148-
- Integration tests live in `tests/` and exercise only the public API — they are
149-
the regression suite for the crate's contract.
162+
- Module-local unit tests live in `crates/<package>/src/<feature>/test.rs` and
163+
may touch private items.
164+
- Integration tests live in `crates/<package>/tests/` and exercise only the
165+
public API — they are the regression suite for the crate's contract.
150166
- Use descriptive, behavioral test names: `rejects_an_empty_name`, not
151167
`test_greet_2`.
152168
- Cover the failure paths, not just the happy path. Every new error variant
@@ -171,8 +187,8 @@ Write documentation for the reader who has never seen the code.
171187
treats as an error.
172188
- Start every `mod.rs` and `test.rs` with a concise module-level `//!`
173189
description.
174-
- `src/lib.rs` carries the crate-level overview: what the crate does, the
175-
primary entry points, and a short runnable example.
190+
- Each crate's `src/lib.rs` carries its crate-level overview: what the crate
191+
does, its primary entry points, and a short runnable example.
176192
- Prefer concrete examples over vague description. Doc examples are compiled and
177193
run by `cargo test`, so they cannot drift.
178194
- Complex modules must include a module-level `README.md` covering their design,
@@ -221,13 +237,14 @@ explicitly declined with a reason.
221237
Releases run from `.github/workflows/release.yml` via a manual
222238
`workflow_dispatch` with a `patch` / `minor` / `major` bump. The workflow
223239
re-runs the full validation suite, computes the next version, updates
224-
`Cargo.toml` and `Cargo.lock`, commits and tags `vX.Y.Z`, packages, pushes, and
225-
publishes to crates.io using the `CARGO_REGISTRY_TOKEN` secret.
240+
`crates/tinymemory/Cargo.toml` and `Cargo.lock`, commits and tags `vX.Y.Z`,
241+
packages, pushes, and publishes to crates.io using the `CARGO_REGISTRY_TOKEN`
242+
secret.
226243

227244
Consequently:
228245

229-
- Do not hand-edit the `version` field in `Cargo.toml`; the release workflow
230-
owns it.
246+
- Do not hand-edit the `version` field in `crates/tinymemory/Cargo.toml`; the
247+
release workflow owns it.
231248
- Follow semantic versioning. Any change to the public surface that is not
232249
purely additive is a breaking change and needs a major bump (pre-1.0: a minor
233250
bump).

Cargo.lock

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)