change prototype of onBuild, onTest#161
Conversation
There was a problem hiding this comment.
Review: change prototype of onBuild, onTest
The signature simplification (func(ctx *Context) with build state moved onto Context) is clean and consistently propagated through classfile.go, internal/formula/formula.go, build.go, the Go tests, and the zlib demo. However there are a few concrete correctness problems that look like they would fail the build/tests as-is.
Blocking
1. 57 .gox formula files still use the old 3-arg signature — only the zlib demo was migrated.
This PR changes the DSL hook prototype to onBuild ctx => { ... } and drops out, but only formula/demo/zlib/zlib_llar.gox was updated. 57 other .gox files still use onBuild (ctx, proj, out) => / onBuild (proj, out) => with out.addErr / out.setMetadata / installDir, err := ctx.outputDir(). Many are active test fixtures that the loaders type-assert against the new signature — e.g.:
internal/formula/testdata/formula/targetsurface_llar.gox(loaded byTestLoadFS_TargetSurface,TestClone,TestFormulaProgramCleanup)internal/build/testdata/formulas/test/liba/1.0.0/Liba_llar.goxand siblings (loaded acrossinternal/buildtests)cmd/llar/internal/testdata/formulas/**
internal/formula/formula.go does valueOf(class, "fOnBuild").(func(*formula.Context)); a fixture compiled with the old arity will either fail to compile against the new OnBuild gopt or panic on that type assertion. These fixtures must be migrated in the same PR, otherwise the test suite breaks. (grep -rl 'onBuild (ctx, proj\|onBuild (proj\|out.addErr\|out.setMetadata\|ctx.outputDir()' --include='*.gox' → 57 files.)
2. errors.Is no longer reaches the wrapped cause — breaks the existing OnTest test.
See inline comment on internal/build/build.go. buildContext.Errs is github.com/qiniu/x/errors.List (type List []error), which has no Unwrap(). The old code wrapped errors.Join(testOut.Errs()...) (which errors.Is can walk); the new %w-wrap of List cannot be traversed, so the unchanged errors.Is(err, wantErr) assertion in TestBuild_RunTest_EnabledSurfacesOnTestError (build_test.go:956) will fail.
Should fix
3. OutputDir__1 panics across a non-recovering boundary.
See inline comment on formula/project.go. There is no recover() anywhere in the repo, and on the HTTP build path Builder.Build runs inside a singleflight.DoChan goroutine (internal/build/http/http.go:107), where an unrecovered panic crashes the whole server process rather than surfacing as a build error. The c.Errs.Add(err) before panic(err) is also dead — nothing reads Errs once the panic unwinds.
Minor
- Shared-context error attribution (
internal/build/build.go:305,:316): OnBuild and OnTest now write into the samebuildContext.Errs, and both phases testlen(buildContext.Errs) > 0. It works today only because OnBuild returns early on error. If a future change lets control reach OnTest with pre-existing errors (orOutputDir__1records without panicking), a build-phase error would be mis-reported as "onTest failed". Consider snapshotting the length before OnTest, or a per-phase list. - Stale docs not updated with the new signature:
AGENTS.md:64(onBuild (ctx, proj, out) =>),AGENTS.md:88(this.OnBuild(func(ctx *formula.Context, proj *formula.Project, out *formula.BuildResult)), anddoc/formula.md:24(onBuild (proj, out) =>).
| return fmt.Errorf("onTest failed for %s@%s: %w", mod.Path, mod.Version, errors.Join(testOut.Errs()...)) | ||
| mod.OnTest(buildContext) | ||
| if len(buildContext.Errs) > 0 { | ||
| return fmt.Errorf("onTest failed for %s@%s: %w", mod.Path, mod.Version, buildContext.Errs) |
There was a problem hiding this comment.
buildContext.Errs is qiniu/x/errors.List (type List []error), which does not implement Unwrap(). Wrapping it with %w produces a chain that errors.Is cannot descend past, so errors.Is(err, cause) no longer finds the injected error.
The unchanged assertion in TestBuild_RunTest_EnabledSurfacesOnTestError (build_test.go:956) — if !errors.Is(err, wantErr) — will fail with this change. The previous code used errors.Join(testOut.Errs()...), which yields an Unwrap() []error chain that errors.Is can walk.
Suggest wrapping buildContext.Errs.ToError() (returns the single error directly when len == 1) or keeping errors.Join(buildContext.Errs...) so the cause stays inspectable.
| dir, err := c.getOutputDir(c.matrixStr, mod) | ||
| if err != nil { | ||
| c.Errs.Add(err) | ||
| panic(err) |
There was a problem hiding this comment.
Two concerns with panic(err) here:
- Unrecovered panic can crash the process.
OnBuild/OnTestrun insideexecbroker.Do(...)ininternal/build/build.go, and there is norecover()anywhere in the repo.ctx.outputDir(dep)can legitimately fail (e.g. an undeclared dependency), so this panics on a routine, input-triggerable error. On the HTTP build pathBuilder.Buildruns inside asingleflight.DoChangoroutine (internal/build/http/http.go:107) — a panic there is not recovered bynet/httpand takes down the whole server. Prefer recording intoc.Errsand returning""(the existinglen(buildContext.Errs) > 0check then surfaces it), or add arecoverboundary around the hook invocation. - Dead
Errs.Add. Because the panic is never recovered, thec.Errs.Add(err)on the line above is never read. Either surface viaErrsand return, or panic without the redundantAdd.
Also: the doc comment (// OutputDir__1 returns ... // In DSL: ctx.outputDir(dep)) no longer mentions that this now panics on failure and drops the error return — worth documenting for formula authors.
|
|
||
| // NewContext creates a Context with build-internal fields. | ||
| func NewContext(sourceDir, installDir, matrixStr string, getOutputDir func(string, module.Version) (string, error)) *Context { | ||
| func NewContext(proj *Project, sourceDir, installDir, matrixStr string, getOutputDir func(string, module.Version) (string, error)) *Context { |
There was a problem hiding this comment.
The exported NewContext lost its doc comment (// NewContext creates a Context with build-internal fields.) in this change and now gains a new proj parameter. Please restore a doc comment (mentioning the new proj) to keep the exported API documented.
No description provided.