feat(plugin): provider functions can declare targets & provider_state - #165
Open
raphaelvigee wants to merge 1 commit into
Open
feat(plugin): provider functions can declare targets & provider_state#165raphaelvigee wants to merge 1 commit into
raphaelvigee wants to merge 1 commit into
Conversation
raphaelvigee
force-pushed
the
feat/buildfile-plugin-declarations
branch
from
July 30, 2026 09:41
543d880 to
510c371
Compare
A provider function (surfaced as `heph.<provider>.<fn>` in BUILD files) could previously only return a value. It can now also declare `target()` and `provider_state()` via a new `FnOutcome` return bundle, which the buildfile provider merges into the calling package as if hand-written. This is the "build-file plugin" primitive: a tool author can ship a codegen rule as a provider function wrapping the existing `exec` driver, instead of a cdylib. The return-bundle shape is wire-friendly, so out-of-process (e.g. JS) plugins can carry declarations once the ABI is extended to transport them. Also: provider functions now accept named arguments. `invoke` used `parse_positional`, which rejects all named args via `no_named_args`; it now uses `positions()` + `names_map()`, letting a rule take `foo_codegen(name = ..., srcs = ...)`. The declared signature remains the canonical arity/type guard. Declarations are honored in-process only; crossing the plugin ABI with a non-empty declaration set is a hard error until the ABI carries them. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
raphaelvigee
force-pushed
the
feat/buildfile-plugin-declarations
branch
from
August 1, 2026 21:55
510c371 to
931225a
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
Authoring a third-party heph plugin today means writing a second
cdylibcrate that exportsheph_plugin_createagainst the frozenstabbyABI (prost on the cold path, manual log-sink/supervisor exports, anABI_SEMVERbump ceremony, a*-plugin.jsonmanifest with per-arch artifacts + checksums, and apath:/url:config entry thatdlopens it). That's far too heavy for the common case — a tool author (e.g. someone shipping a code generator) who just wants heph users to "pave" their tool: call a rule that runs the tool hermetically with the right inputs/outputs.Most of those "plugins" don't need a new provider or driver at all — they're rules that expand to a
target(driver = "exec", ...). BUILD files are already full Starlark (def+load()), and providers already expose functions asheph.<provider>.<fn>(...). The one missing capability: a provider function could return a value but could not declare a target.What
This adds that capability — the foundational primitive for "build-file plugins" (and, later, out-of-process JS plugins):
ProviderFn::callnow returns anFnOutcome(crates/plugin/src/provider.rs): the value substituted at the call site plus anytarget()/provider_state()the call declared (DeclaredTarget/DeclaredState). Value-only functions build it withValue::into/FnOutcome::value(the common case —glob,join,go.build_addr, …).ProviderNativeFn::invoke) through the same sinks thetarget()/provider_state()builtins use, so a declared target is indistinguishable from a hand-written one. Call-site provenance is captured (when enabled) and stamped on each declared target, so tooling/LSP traces them back to theheph.<plugin>.<fn>(…)call.invokeusedparse_positional, which rejects all named args viano_named_args, so functions were positional-only. It now usespositions()+names_map(); the declaredFnSignatureremains the canonical arity/type guard. This is what lets a rule takefoo_codegen(name = ..., srcs = ...).Design notes
CallFunction) carries the return value alone. Crossing it with a non-empty declaration set is a hard error (both the stabby host and the SDK guest serve path), not a silent drop — extending the ABI to carry declarations is the follow-up that unlocks out-of-process plugins.Example (what a tool author ships)
A provider function
heph.codegen.rule(...)that stands up anexectarget:expands to a fully configured
target(driver = "exec", run = [...], deps = {...}, out = {...}, codegen = "copy")in the calling package — no cdylib.Scope / follow-ups
CallFunctionResponse) to carry declarations for out-of-process plugins; a concrete build-file/JS plugin authoring surface on top.Tests
plugin-buildfile: a provider function declares a target +provider_stateand both land in the package; empty declared name fails loudly; provider functions accept named args (guards thepositions()/names_map()change). Existing provider-function tests still green (47 total).FnOutcomepropagation verified acrossbuiltins(69),plugin-go(4),plugin-sdkw/stabby(17),plugin-stabby(1).lint+ stabby-gated clippy +fmtclean.🤖 Generated with Claude Code