OschemaBuildConfig: expose extra_lib_modules knob so consumers can keep hand-written modules at the lib root #145

Open
opened 2026-05-27 19:12:55 +00:00 by mik-tf · 0 comments
Owner

What I saw

generate_lib_rs() at crates/generator/src/build/builder.rs:444 unconditionally rewrites src/lib.rs every build with content that lists only the configured domain modules. Any hand-written module declaration at the lib root (e.g. pub mod util; that a downstream consumer's cloud/rpc.rs imports via use crate::util::*) is silently stripped on every rebuild.

For long-running consumers this is invisible — incremental compilation often skips re-checking the lib target, so the local dev loop doesn't surface the broken crate::util reference. The break only manifests on fresh checkouts (CI on every push), where cargo check --workspace runs against a freshly-regenerated lib.rs and fails with:

error[E0432]: unresolved import `crate::util`
  --> crates/my_compute_zos_server/src/cloud/rpc.rs:11:12
   |
11 | use crate::util::{
   |            ^^^^ could not find `util` in the crate root

Same pattern surfaced on hero_compute today (workspace lhumina_code/hero_compute). Worked around in that repo's build.rs with a post-codegen patch that re-injects pub mod util; — see hero_compute@f05690f. Localized to that one consumer but the pattern will repeat for any other consumer that needs a hand-written module at the lib root.

What I am asking for

Add a builder option to declare extra lib-root modules so the generator emits them alongside the domain modules:

OschemaBuildConfig::new()
    .schemas_dir("../../schemas")
    .domain("cloud", "Hero Compute TFGrid")
    .extra_lib_module("util")         // new
    .generate_server()

generate_lib_rs then emits:

//! ... header ...

pub mod util;       // from extra_lib_module(s), declared first
                    // so the generated lib.rs stays deterministic

#[cfg(feature = "cloud")]
pub mod cloud;

Shape suggestions (any of these is fine, ordered by least to most expressive):

  1. extra_lib_modules: Vec<String> on OschemaBuildConfig plus a with_extra_lib_module(name: impl Into<String>) builder method. Modules are always public, no cfg gate. Covers the common case.
  2. Same as 1 plus an optional cfg predicate per module (e.g. with_extra_lib_module_cfg("util", Some("not(target_arch = "wasm32")"))).
  3. Pull from service.toml [[lib_modules]] so the consumer does not even need to touch build.rs. Optional, lower priority.

Why this matters

The current behavior makes it impossible to maintain hand-written lib-root modules without a build.rs post-process. Every consumer hitting this has to invent the same workaround independently, and the failure mode (silent erase, broken CI on fresh checkouts) is exactly the kind that bites once per CI run rather than during dev.

The nested_layout: true config flag already documents an opt-out from lib.rs generation entirely (config.rs:102-105), which suggests the project already recognizes the need. extra_lib_modules is the lighter-weight counterpart for consumers using the flat layout.

Acceptance

  • OschemaBuildConfig exposes the new knob (whichever shape above).
  • generate_lib_rs emits declared extra modules before the feature-gated domain modules.
  • cargo fmt --check clean on the emitted output.
  • hero_compute's build.rs workaround can be replaced with the new builder call and the inline patch removed.

Filed by mik-tf during home#238 admin/tester UX work; the workaround on hero_compute@f05690f unblocked the deployer arc while this lands. Happy to send the PR if it helps.

## What I saw `generate_lib_rs()` at `crates/generator/src/build/builder.rs:444` unconditionally rewrites `src/lib.rs` every build with content that lists only the configured domain modules. Any hand-written module declaration at the lib root (e.g. `pub mod util;` that a downstream consumer's `cloud/rpc.rs` imports via `use crate::util::*`) is silently stripped on every rebuild. For long-running consumers this is invisible — incremental compilation often skips re-checking the lib target, so the local dev loop doesn't surface the broken `crate::util` reference. The break only manifests on fresh checkouts (CI on every push), where `cargo check --workspace` runs against a freshly-regenerated `lib.rs` and fails with: ``` error[E0432]: unresolved import `crate::util` --> crates/my_compute_zos_server/src/cloud/rpc.rs:11:12 | 11 | use crate::util::{ | ^^^^ could not find `util` in the crate root ``` Same pattern surfaced on hero_compute today (workspace `lhumina_code/hero_compute`). Worked around in that repo's `build.rs` with a post-codegen patch that re-injects `pub mod util;` — see `hero_compute@f05690f`. Localized to that one consumer but the pattern will repeat for any other consumer that needs a hand-written module at the lib root. ## What I am asking for Add a builder option to declare extra lib-root modules so the generator emits them alongside the domain modules: ```rust OschemaBuildConfig::new() .schemas_dir("../../schemas") .domain("cloud", "Hero Compute TFGrid") .extra_lib_module("util") // new .generate_server() ``` `generate_lib_rs` then emits: ```rust //! ... header ... pub mod util; // from extra_lib_module(s), declared first // so the generated lib.rs stays deterministic #[cfg(feature = "cloud")] pub mod cloud; ``` Shape suggestions (any of these is fine, ordered by least to most expressive): 1. `extra_lib_modules: Vec<String>` on `OschemaBuildConfig` plus a `with_extra_lib_module(name: impl Into<String>)` builder method. Modules are always public, no cfg gate. Covers the common case. 2. Same as 1 plus an optional `cfg` predicate per module (e.g. `with_extra_lib_module_cfg("util", Some("not(target_arch = "wasm32")"))`). 3. Pull from `service.toml` `[[lib_modules]]` so the consumer does not even need to touch `build.rs`. Optional, lower priority. ## Why this matters The current behavior makes it impossible to maintain hand-written lib-root modules without a `build.rs` post-process. Every consumer hitting this has to invent the same workaround independently, and the failure mode (silent erase, broken CI on fresh checkouts) is exactly the kind that bites once per CI run rather than during dev. The `nested_layout: true` config flag already documents an opt-out from `lib.rs` generation entirely (`config.rs:102-105`), which suggests the project already recognizes the need. `extra_lib_modules` is the lighter-weight counterpart for consumers using the flat layout. ## Acceptance - `OschemaBuildConfig` exposes the new knob (whichever shape above). - `generate_lib_rs` emits declared extra modules before the feature-gated domain modules. - `cargo fmt --check` clean on the emitted output. - hero_compute's `build.rs` workaround can be replaced with the new builder call and the inline patch removed. Filed by mik-tf during home#238 admin/tester UX work; the workaround on `hero_compute@f05690f` unblocked the deployer arc while this lands. Happy to send the PR if it helps.
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_blueprint#145
No description provided.