Modularize 2624-line generator/src/build/build.rs #56

Closed
opened 2026-05-18 12:37:10 +00:00 by timur · 5 comments
Owner

Problem

crates/generator/src/build/build.rs is a 2624-line single file handling schema parsing, six code generators (Rust types, OSIS server, RPC, OpenRPC, JS/TS, Rhai), directory creation, file emission, rustfmt invocation, and the OschemaBuilder public API. It is hard to extend, hard to debug, and forces every change to load the full file into context.

Proposed split

Keep the public surface (OschemaBuildConfig, OschemaBuilder) stable so service-side build.rs files don't change. Move internals into modules under crates/generator/src/build/:

build/
  mod.rs              — re-export OschemaBuildConfig + OschemaBuilder (public API only)
  config.rs           — OschemaBuildConfig builder (currently ~400 lines of options)
  builder.rs          — OschemaBuilder orchestration loop
  emit/
    mod.rs
    rust_types.rs     — types_generated.rs + types_wasm_generated.rs
    rust_server.rs    — osis_server_generated.rs
    rust_rpc.rs       — rpc_generated.rs + preserved rpc.rs stub
    openrpc.rs        — schema.openrpc.json per domain
    js.rs             — sdk/js output
    rhai.rs           — rhai bindings crate scaffold
  fs.rs               — directory creation, rustfmt invocation, file write helpers
  layout.rs           — nested_layout, single_bin, bin_prefix path math

Each emit/*.rs should be < 400 lines and own one output target.

Side benefit

Letting fs.rs own rustfmt invocation and output-path defaults lets the service-side build.rs collapse from 42 lines (current recipe_server/build.rs) to ~3:

fn main() {
    hero_rpc_osis::build::OschemaBuilder::from_service_toml().generate().unwrap();
}

This is half of why the meeting asked for "smaller build.rs" — see issue body of #scaffold for the other half.

Acceptance

  • No file under crates/generator/src/build/ exceeds ~500 lines.
  • recipe_server builds unchanged (its current 42-line build.rs keeps working).
  • A new build.rs shape (≤5 lines, no output-path constants) also works.
  • cargo test -p hero_rpc_generator stays green.

Notes

Refactor only — no behaviour change. If a behaviour change is needed (e.g., to support the ≤5-line build.rs), open a follow-up referencing the codegen-alignment issue.

## Problem `crates/generator/src/build/build.rs` is a 2624-line single file handling schema parsing, six code generators (Rust types, OSIS server, RPC, OpenRPC, JS/TS, Rhai), directory creation, file emission, rustfmt invocation, and the `OschemaBuilder` public API. It is hard to extend, hard to debug, and forces every change to load the full file into context. ## Proposed split Keep the public surface (`OschemaBuildConfig`, `OschemaBuilder`) stable so service-side `build.rs` files don't change. Move internals into modules under `crates/generator/src/build/`: ``` build/ mod.rs — re-export OschemaBuildConfig + OschemaBuilder (public API only) config.rs — OschemaBuildConfig builder (currently ~400 lines of options) builder.rs — OschemaBuilder orchestration loop emit/ mod.rs rust_types.rs — types_generated.rs + types_wasm_generated.rs rust_server.rs — osis_server_generated.rs rust_rpc.rs — rpc_generated.rs + preserved rpc.rs stub openrpc.rs — schema.openrpc.json per domain js.rs — sdk/js output rhai.rs — rhai bindings crate scaffold fs.rs — directory creation, rustfmt invocation, file write helpers layout.rs — nested_layout, single_bin, bin_prefix path math ``` Each `emit/*.rs` should be < 400 lines and own one output target. ## Side benefit Letting `fs.rs` own rustfmt invocation and output-path defaults lets the service-side `build.rs` collapse from 42 lines (current `recipe_server/build.rs`) to ~3: ```rust fn main() { hero_rpc_osis::build::OschemaBuilder::from_service_toml().generate().unwrap(); } ``` This is half of why the meeting asked for "smaller build.rs" — see issue body of #scaffold for the other half. ## Acceptance - No file under `crates/generator/src/build/` exceeds ~500 lines. - `recipe_server` builds unchanged (its current 42-line `build.rs` keeps working). - A new `build.rs` shape (≤5 lines, no output-path constants) also works. - `cargo test -p hero_rpc_generator` stays green. ## Notes Refactor only — no behaviour change. If a behaviour change is needed (e.g., to support the ≤5-line build.rs), open a follow-up referencing the codegen-alignment issue.
Author
Owner

Parent / context tracker: hero_skills#262 — read it before starting work on this issue. Locked decisions, reference materials, and execution order live there. Iterate via comments here; consolidation passes on the body only after feedback settles.

Parent / context tracker: [hero_skills#262](https://forge.ourworld.tf/lhumina_code/hero_skills/issues/262) — read it before starting work on this issue. Locked decisions, reference materials, and execution order live there. Iterate via comments here; consolidation passes on the body only after feedback settles.
Author
Owner

State refresh

Pulled latest. crates/generator/src/build/ already has some structure: mod.rs (40 lines), scaffold.rs (1208 lines) split out separately. But build.rs is still 2653 lines (+29 vs my original count) — modularization not yet done.

This issue is still valid as written. The split is the gentler one I proposed since the directory structure already exists. Just need to extract the per-target emitters out of build.rs into build/emit/{rust_types,rust_server,rust_rpc,openrpc,js,rhai,python}.rs plus build/fs.rs + build/layout.rs.

Adding Python emitter to the split list (per #55 comment 33682).

## State refresh Pulled latest. `crates/generator/src/build/` already has some structure: `mod.rs` (40 lines), `scaffold.rs` (1208 lines) split out separately. But `build.rs` is **still 2653 lines** (+29 vs my original count) — modularization not yet done. This issue is still valid as written. The split is the gentler one I proposed since the directory structure already exists. Just need to extract the per-target emitters out of `build.rs` into `build/emit/{rust_types,rust_server,rust_rpc,openrpc,js,rhai,python}.rs` plus `build/fs.rs` + `build/layout.rs`. Adding Python emitter to the split list (per [#55 comment 33682](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55#issuecomment-33682)).
Author
Owner

Done in 8a8d66d (development)

Split crates/generator/src/build/build.rs (2653 LOC) into focused submodules. Public surface unchanged — recipe_server builds without modification.

File layout & sizes

File LOC Owns
mod.rs 64 public re-exports (identical to old surface)
config.rs 490 OschemaBuildConfig, SingleBinConfig, GenerationTarget + Default + builder methods + 2 unit tests
builder.rs 368 OschemaBuilder::{new, with_manifest_dir, generate} + determine_domains + flat-layout mod.rs/lib.rs writers
error.rs 43 BuildError + BuildResult
fs.rs 32 collect_schema_files
layout.rs 176 pascal_case, title_case_acronym_aware, generate_domain_mod_flat + its unit test
emit/mod.rs 18 submodule declarations
emit/domain.rs 147 per-domain dispatch into crate::generate::Generator
emit/bin.rs 433 per-domain bins + single-binary orchestrator
emit/rust_server.rs 143 server-crate mod.rs + lib.rs
emit/rust_sdk.rs 363 Rust SDK / client-crate scaffolding
emit/docs.rs 68 docs README.md index
emit/services.rs 36 preserved src/services/mod.rs placeholder

All new files are under the issue's ~500-LOC target (largest: config.rs 490, emit/bin.rs 433). scaffold.rs (1208) is unchanged — out of scope per the issue.

Acceptance check

  • No file under build/ exceeds ~500 lines (except pre-existing scaffold.rs).
  • recipe_server builds unchanged (its 42-line build.rs keeps working — no edits).
  • cargo test -p hero_rpc_generator green (105 passed).
  • cargo clippy -p hero_rpc_generator --all-targets clean (preserved the ptr_arg allow that was on the old mod build).
  • cargo build --workspace green.
  • ≤5-line build.rs shape — not in scope here: that needs OschemaBuildConfig::from_service_toml(), which doesn't exist yet and is part of #55 (codegen alignment + service.toml source-of-truth). Tracked there.

Naming deviation — flag for review

The issue body proposed emit/{rust_types,rust_rpc,openrpc,js,rhai,python}.rs — these names describe per-output-target emission. But in this crate, that code lives in crate::generate::Generator, not in build.rs. The build.rs file only orchestrates: it configures Generator, then writes the scaffolding around it (bins, server-crate mods, SDK crate, docs index).

So I split along what build.rs actually does. Emit-tree concerns map cleanly:

  • emit/bin.rs — owns both bin shapes (per-domain + single-bin orchestrator)
  • emit/rust_server.rs — server-crate mods (kept the name from the issue)
  • emit/rust_sdk.rs — SDK/client crate scaffolding
  • emit/docs.rs, emit/services.rs, emit/domain.rs — supporting emitters

Pushing per-target emission out of Generator into emit/{rust_types,rust_rpc,openrpc,js,rhai,python}.rs would be a much larger refactor crossing the build/ boundary, with behaviour-change risk. The issue says "Refactor only — no behaviour change." so I left Generator untouched. If you want that bigger split, happy to open a follow-up issue.

Python emitter (per #55 comment 33682) is not added here — it doesn't exist yet. That's part of #55.

## Done in 8a8d66d (development) Split `crates/generator/src/build/build.rs` (2653 LOC) into focused submodules. **Public surface unchanged** — recipe_server builds without modification. ### File layout & sizes | File | LOC | Owns | |---|---:|---| | `mod.rs` | 64 | public re-exports (identical to old surface) | | `config.rs` | 490 | `OschemaBuildConfig`, `SingleBinConfig`, `GenerationTarget` + Default + builder methods + 2 unit tests | | `builder.rs` | 368 | `OschemaBuilder::{new, with_manifest_dir, generate}` + `determine_domains` + flat-layout `mod.rs`/`lib.rs` writers | | `error.rs` | 43 | `BuildError` + `BuildResult` | | `fs.rs` | 32 | `collect_schema_files` | | `layout.rs` | 176 | `pascal_case`, `title_case_acronym_aware`, `generate_domain_mod_flat` + its unit test | | `emit/mod.rs` | 18 | submodule declarations | | `emit/domain.rs` | 147 | per-domain dispatch into `crate::generate::Generator` | | `emit/bin.rs` | 433 | per-domain bins + single-binary orchestrator | | `emit/rust_server.rs` | 143 | server-crate `mod.rs` + `lib.rs` | | `emit/rust_sdk.rs` | 363 | Rust SDK / client-crate scaffolding | | `emit/docs.rs` | 68 | docs `README.md` index | | `emit/services.rs` | 36 | preserved `src/services/mod.rs` placeholder | All new files are under the issue's ~500-LOC target (largest: `config.rs` 490, `emit/bin.rs` 433). `scaffold.rs` (1208) is unchanged — out of scope per the issue. ### Acceptance check - [x] No file under `build/` exceeds ~500 lines (except pre-existing `scaffold.rs`). - [x] `recipe_server` builds unchanged (its 42-line `build.rs` keeps working — no edits). - [x] `cargo test -p hero_rpc_generator` green (105 passed). - [x] `cargo clippy -p hero_rpc_generator --all-targets` clean (preserved the `ptr_arg` allow that was on the old `mod build`). - [x] `cargo build --workspace` green. - [ ] `≤5-line build.rs` shape — **not in scope here**: that needs `OschemaBuildConfig::from_service_toml()`, which doesn't exist yet and is part of #55 (codegen alignment + `service.toml` source-of-truth). Tracked there. ### Naming deviation — flag for review The issue body proposed `emit/{rust_types,rust_rpc,openrpc,js,rhai,python}.rs` — these names describe **per-output-target emission**. But in this crate, that code lives in `crate::generate::Generator`, not in `build.rs`. The build.rs file only orchestrates: it configures `Generator`, then writes the *scaffolding* around it (bins, server-crate mods, SDK crate, docs index). So I split along what `build.rs` actually does. Emit-tree concerns map cleanly: - `emit/bin.rs` — owns both bin shapes (per-domain + single-bin orchestrator) - `emit/rust_server.rs` — server-crate mods (kept the name from the issue) - `emit/rust_sdk.rs` — SDK/client crate scaffolding - `emit/docs.rs`, `emit/services.rs`, `emit/domain.rs` — supporting emitters Pushing per-target emission *out* of `Generator` into `emit/{rust_types,rust_rpc,openrpc,js,rhai,python}.rs` would be a much larger refactor crossing the `build/` boundary, with behaviour-change risk. The issue says "Refactor only — no behaviour change." so I left `Generator` untouched. If you want that bigger split, happy to open a follow-up issue. **Python emitter** (per [#55 comment 33682](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/55#issuecomment-33682)) is not added here — it doesn't exist yet. That's part of #55.
Author
Owner

Opened follow-up #58 for the deferred work — pushing per-target emission code out of crate::generate::Generator into build/emit/{rust_types,rust_rpc,openrpc,js,rhai,python}.rs, matching the original file-naming proposal here.

Opened follow-up [#58](https://forge.ourworld.tf/lhumina_code/hero_rpc/issues/58) for the deferred work — pushing per-target emission code out of `crate::generate::Generator` into `build/emit/{rust_types,rust_rpc,openrpc,js,rhai,python}.rs`, matching the original file-naming proposal here.
Author
Owner

Closing — work landed. Merged in 8a8d66d on 2026-05-18 (build.rs modularized into 12 sub-modules). Per-target Generator emitter refactor tracked separately in #59.

Closing — work landed. Merged in 8a8d66d on 2026-05-18 (build.rs modularized into 12 sub-modules). Per-target Generator emitter refactor tracked separately in #59.
timur closed this issue 2026-05-19 13:40:25 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
lhumina_code/hero_rpc#56
No description provided.