Skip to content

feat(toolkit-macros): compile-time gear dependency linking via re-exports#4146

Open
maurolacy wants to merge 6 commits into
constructorfabric:mainfrom
maurolacy:feature/gears-deps
Open

feat(toolkit-macros): compile-time gear dependency linking via re-exports#4146
maurolacy wants to merge 6 commits into
constructorfabric:mainfrom
maurolacy:feature/gears-deps

Conversation

@maurolacy

@maurolacy maurolacy commented Jun 26, 2026

Copy link
Copy Markdown

Problem

The deps field in #[toolkit::gear(deps = [...])] declares runtime lifecycle
dependencies between gears. At startup, build_dependency_graph() validates that
every declared dep exists in the inventory-based registry. However, inventory
relies on linker-section tricks: only crates explicitly referenced with use X as _;
in the generated main.rs survive the linker. Transitive Cargo dependencies get
their inventory::submit! registrations silently stripped.

This forces users to flatten the entire gear dependency tree in Gears.toml. For
example, listing api-gateway is not enough. You must also manually list
grpc-hub, authn-resolver, types-registry, and every other transitive dep.
Forgetting one causes a runtime UnknownDependency error at startup, with no
compile-time signal that anything is wrong.

Solution

The #[toolkit::gear] macro now generates pub use re-exports for each declared
dependency:

#[toolkit::gear(name = "api-gateway", deps = ["grpc-hub", "authn-resolver"])]

expands to (among other things):

#[cfg(not(test))]
#[doc(hidden)]
pub use grpc_hub as _gear_dep_grpc_hub;

#[cfg(not(test))]
#[doc(hidden)]
pub use authn_resolver as _gear_dep_authn_resolver;

This forces the linker to keep the dependency gear crates (and their
inventory::submit! registrations) alive when a gear is pulled in transitively.
The re-exports chain: if api-gateway re-exports authn-resolver, and
authn-resolver re-exports types-registry, then pulling in api-gateway alone
is sufficient to register the entire transitive dependency tree.

The re-exports are gated behind #[cfg(not(test))], so that test builds (where
gears are often defined in the same compilation unit) don't require phantom
external crate dependencies.

Trade-off: Cargo.toml dependencies

For the pub use to compile, each gear's Cargo.toml must list its dep gear
crates as Cargo dependencies (not just SDKs). This is a new requirement, but
forgetting a dependency is now a compile-time error (no external crate),
not a silent runtime failure. The SDK dependencies remain necessary for the
actual API types used in gear implementations.

Naming convention

This change relies on a strict, bidirectional naming convention:

Gear name Crate name Lib name
api-gateway cf-gears-api-gateway api_gateway
types-registry cf-gears-types-registry types_registry

The macro derives the lib name from the dep's gear name by replacing - with _.
This convention is already followed by all gear crates in the repo (24/27 gears;
the 3 exceptions are internal plugins in mini-chat which are same-crate and
unaffected).

Changes

  • libs/toolkit-macros/src/lib.rs: Generate pub use re-exports for each
    entry in deps = [...], gated behind #[cfg(not(test))].
  • Cargo.toml (root): Added 8 workspace-level dependency entries for gear
    crates that were previously only available as SDKs.
  • 22 gear Cargo.toml files: Added the full gear crate as a dependency
    (via { workspace = true }) for each declared dep.

Verification

  • cargo build: Entire workspace compiles cleanly.
  • cargo test --workspace: All test suites pass.

What this enables

With this change, cargo gears and future tooling no longer needs to resolve
and flatten the transitive gear dependency tree. Users can list only the gears
they directly use in Gears.toml, and the macro-generated re-exports ensure the
full dependency chain is linked.

Summary by CodeRabbit

  • New Features

    • Expanded workspace support for several system components, making them available across the project.
    • Added support for additional example and toolkit modules to use the shared workspace setup.
  • Bug Fixes

    • Improved dependency linking so transitively included components keep their registrations available at runtime.
    • Updated multiple modules to include shared registry and resolver support, reducing missing-integration issues.

Mauro Lacy added 5 commits June 26, 2026 08:15
Signed-off-by: Mauro Lacy <mauro.lacy@acronis.com>
Signed-off-by: Mauro Lacy <mauro.lacy@acronis.com>
Signed-off-by: Mauro Lacy <mauro.lacy@acronis.com>
Signed-off-by: Mauro Lacy <mauro.lacy@acronis.com>
Signed-off-by: Mauro Lacy <mauro.lacy@acronis.com>
@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@maurolacy, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 35 minutes. Learn how PR review limits work.

To continue reviewing without waiting, enable usage-based billing in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ab0cb1ad-ceca-4245-8386-6ecf19a49386

📥 Commits

Reviewing files that changed from the base of the PR and between 8aa9b1f and 7a7822c.

📒 Files selected for processing (1)
  • libs/toolkit-macros/src/lib.rs
📝 Walkthrough

Walkthrough

The workspace adds eight new system crate entries, and multiple gears, plugins, and examples add workspace or direct dependencies on the new resolver, registry, and gateway crates. The #[gear] macro now emits hidden dependency re-exports in generated code.

Changes

Gear dependency wiring

Layer / File(s) Summary
Workspace dependency registry
Cargo.toml
[workspace.dependencies] adds eight pinned system crate entries.
Core system manifests
gears/system/authn-resolver/*/Cargo.toml, gears/system/authz-resolver/*/Cargo.toml, gears/system/tenant-resolver/*/Cargo.toml, gears/system/resource-group/resource-group/Cargo.toml, gears/credstore/*/Cargo.toml
Core resolver, resource-group, and credstore crates plus their plugins add workspace dependencies on types-registry and related resolver crates.
System application manifests
gears/system/account-management/*/Cargo.toml, gears/system/api-gateway/Cargo.toml, gears/system/oagw/oagw/Cargo.toml, gears/mini-chat/mini-chat/Cargo.toml, gears/simple-user-settings/simple-user-settings/Cargo.toml, gears/system/usage-collector/*/Cargo.toml
Account-management, api-gateway, oagw, mini-chat, simple-user-settings, and usage-collector manifests add the new workspace or direct dependencies.
Example manifests
examples/oop-gears/calculator-gateway/calculator-gateway/Cargo.toml, examples/toolkit/users-info/users-info/Cargo.toml
The example gateway and toolkit manifests add the local calculator dependency and the authz-resolver workspace dependency.
Gear macro re-exports
libs/toolkit-macros/src/lib.rs
The #[gear] macro builds hidden dependency re-export statements from deps_owned and appends them to the generated token stream.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • Artifizer
  • striped-zebra-dev

Poem

I hop through crates where new paths bloom,
and tiny deps light up the room.
The gear macro stitched a hidden seam,
so inventory sings in the build-time stream.
🐇

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: compile-time dependency linking in toolkit-macros via re-exports.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
libs/toolkit-macros/src/lib.rs (1)

771-787: 🩺 Stability & Availability | 🔵 Trivial

Anchor the re-export path with a leading :: to avoid module-scope shadowing.

The generated pub use #crate_identas#alias_ident; expands at the call site of the #[gear] macro. If the macro is invoked within a submodule that defines a local item (e.g., mod crate_name or const crate_name), the unqualified use resolution may resolve to that local item instead of the intended extern crate. Using ::#crate_ident`` forces resolution against the extern prelude, ensuring the re-export remains stable regardless of local scope.

♻️ Proposed change
             quote! {
                 #[cfg(not(test))]
                 #[doc(hidden)]
-                pub use `#crate_ident` as `#alias_ident`;
+                pub use ::`#crate_ident` as `#alias_ident`;
             }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@libs/toolkit-macros/src/lib.rs` around lines 771 - 787, The generated
re-export in the macro expansion is currently using an unqualified path that can
be shadowed by local module items. Update the re-export inside the dep reexports
generation in lib.rs so the pub use in the quote! block anchors the crate path
with a leading ::, using the existing crate_ident and alias_ident symbols, to
ensure the extern crate is always resolved from the extern prelude even when
#[gear] is invoked inside a submodule with a conflicting local name.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@libs/toolkit-macros/src/lib.rs`:
- Around line 771-787: The generated re-export in the macro expansion is
currently using an unqualified path that can be shadowed by local module items.
Update the re-export inside the dep reexports generation in lib.rs so the pub
use in the quote! block anchors the crate path with a leading ::, using the
existing crate_ident and alias_ident symbols, to ensure the extern crate is
always resolved from the extern prelude even when #[gear] is invoked inside a
submodule with a conflicting local name.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ea1176a7-fe6b-4137-a78c-d7bf844fa564

📥 Commits

Reviewing files that changed from the base of the PR and between 650eae8 and 8aa9b1f.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (25)
  • Cargo.toml
  • examples/oop-gears/calculator-gateway/calculator-gateway/Cargo.toml
  • examples/toolkit/users-info/users-info/Cargo.toml
  • gears/credstore/credstore/Cargo.toml
  • gears/credstore/plugins/static-credstore-plugin/Cargo.toml
  • gears/mini-chat/mini-chat/Cargo.toml
  • gears/simple-user-settings/simple-user-settings/Cargo.toml
  • gears/system/account-management/account-management/Cargo.toml
  • gears/system/account-management/plugins/static-idp-plugin/Cargo.toml
  • gears/system/api-gateway/Cargo.toml
  • gears/system/authn-resolver/authn-resolver/Cargo.toml
  • gears/system/authn-resolver/plugins/oidc-authn-plugin/Cargo.toml
  • gears/system/authn-resolver/plugins/static-authn-plugin/Cargo.toml
  • gears/system/authz-resolver/authz-resolver/Cargo.toml
  • gears/system/authz-resolver/plugins/static-authz-plugin/Cargo.toml
  • gears/system/authz-resolver/plugins/tr-authz-plugin/Cargo.toml
  • gears/system/oagw/oagw/Cargo.toml
  • gears/system/resource-group/resource-group/Cargo.toml
  • gears/system/tenant-resolver/plugins/rg-tr-plugin/Cargo.toml
  • gears/system/tenant-resolver/plugins/single-tenant-tr-plugin/Cargo.toml
  • gears/system/tenant-resolver/plugins/static-tr-plugin/Cargo.toml
  • gears/system/tenant-resolver/tenant-resolver/Cargo.toml
  • gears/system/usage-collector/plugins/noop-usage-collector-plugin/Cargo.toml
  • gears/system/usage-collector/usage-collector/Cargo.toml
  • libs/toolkit-macros/src/lib.rs

Signed-off-by: Mauro Lacy <mauro.lacy@acronis.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant