fix(build): keep the index tools free of the Wails application

The v3 migration put application.Get() in backend/events and a
ServiceStartup hook in backend/explore, both of which cmd/indexbuild
reaches. v3's application package is GTK/WebKit bindings on Linux, so
the index-artifact job — a plain golang container with CGO_ENABLED=0,
on the stated grounds that neither command imports the app — stopped
compiling with "undefined: pointer". That job owns the ~205 GB dump
checkpoint, so it is the worst place to learn this.

Both are behind the indexbuild tag now: the one app.Event.Emit lives in
runtime_wails.go, runtime_indexbuild.go answers ErrNoRuntime (what the
app itself returns before Run, so Deliver's callers need no second
path), and explore's ServiceStartup moves to its own tagged file.

TestIndexToolsDoNotImportWails walks `go list -deps -tags indexbuild`
so the claim the workflow makes is checked rather than assumed.
This commit is contained in:
2026-08-16 14:51:01 -04:00
parent dd17a4d8eb
commit b98840ee37
9 changed files with 173 additions and 31 deletions
+3 -14
View File
@@ -4,8 +4,6 @@ import (
"context"
"errors"
"log/slog"
"github.com/wailsapp/wails/v3/pkg/application"
)
// ErrNoRuntime is returned by Deliver when there is neither a test Sink
@@ -82,16 +80,7 @@ func Deliver(ctx context.Context, name string, data ...any) error {
}
}
// application.Get() returns nil when no app is running — under test,
// before Run, and after shutdown. It does not terminate the
// process, which is what the v2 probe of the private "events"
// context key existed to avoid.
app := application.Get()
if app == nil {
return ErrNoRuntime
}
app.Event.Emit(name, data...)
return nil
// emitRuntime is the only place the Wails application is touched,
// and it is behind a build tag: see runtime_wails.go.
return emitRuntime(name, data...)
}
+5 -2
View File
@@ -8,9 +8,12 @@ import (
)
// allowedEmitters are the only files permitted to call the Wails
// runtime's event emitter directly.
// runtime's event emitter directly. There is one call and it lives in
// runtime_wails.go rather than emit.go because naming the application
// package is what forces cgo, and cmd/indexbuild builds this package
// without it.
var allowedEmitters = map[string]bool{
filepath.Join("backend", "events", "emit.go"): true,
filepath.Join("backend", "events", "runtime_wails.go"): true,
}
// TestNoDirectRuntimeEmits fails if anything outside backend/events
+18
View File
@@ -0,0 +1,18 @@
//go:build indexbuild
package events
// emitRuntime is the index tools' half of the split described in
// runtime_wails.go: under the indexbuild tag there is no Wails
// application to emit through, and importing the one that would be
// there costs cgo and a GTK/WebKit toolchain the index build container
// deliberately does not have.
//
// Returning ErrNoRuntime is the same answer the app gives before Run
// and after shutdown, so Emit's callers need no second code path: an
// event emitted by cmd/indexbuild is logged and dropped. A test that
// wants to observe one installs a Sink with WithSink, which Deliver
// consults first and which works under either tag.
func emitRuntime(_ string, _ ...any) error {
return ErrNoRuntime
}
+32
View File
@@ -0,0 +1,32 @@
//go:build !indexbuild
package events
import (
"github.com/wailsapp/wails/v3/pkg/application"
)
// emitRuntime pushes an event through the running Wails application.
//
// It is the app's half of a two-file split, and the split exists for a
// build constraint rather than a design one: v3's application package
// is cgo on Linux, so anything importing it needs GTK and WebKit
// headers to compile. cmd/indexbuild and cmd/indexexport reach this
// package through backend/explore and are built in a plain golang
// container with CGO_ENABLED=0 (.gitea/workflows/index-artifact.yml),
// where that is not available and not wanted. See
// runtime_indexbuild.go for the other half.
func emitRuntime(name string, data ...any) error {
// application.Get() returns nil when no app is running — under
// test, before Run, and after shutdown. It does not terminate the
// process, which is what the v2 probe of the private "events"
// context key existed to avoid.
app := application.Get()
if app == nil {
return ErrNoRuntime
}
app.Event.Emit(name, data...)
return nil
}
+3 -14
View File
@@ -9,7 +9,6 @@ import (
"sync"
"time"
"github.com/wailsapp/wails/v3/pkg/application"
"golang.org/x/sync/singleflight"
"yellowjacket/backend/database"
@@ -144,19 +143,9 @@ func (e *Service) CAALimiter() *RateLimiter {
return e.caaLimiter
}
// ServiceStartup is v3's service lifecycle hook: it runs once the
// runtime exists, and ctx is cancelled when the app shuts down. It
// replaces v2's SetContext, which had to be called by hand from
// OnStartup and was exported, so it was also bound to the frontend.
func (e *Service) ServiceStartup(
ctx context.Context,
_ application.ServiceOptions,
) error {
e.ctx = ctx
e.index.SetContext(ctx)
return nil
}
// The v3 service lifecycle hook that fills e.ctx lives in
// servicestartup.go, behind a build tag: naming
// application.ServiceOptions is what would drag cgo into cmd/indexbuild.
// StartIndexBuild kicks off the background search index build.
// Call this after the library scan completes so the indexer doesn't
+30
View File
@@ -0,0 +1,30 @@
//go:build !indexbuild
package explore
import (
"context"
"github.com/wailsapp/wails/v3/pkg/application"
)
// ServiceStartup is v3's service lifecycle hook: it runs once the
// runtime exists, and ctx is cancelled when the app shuts down. It
// replaces v2's SetContext, which had to be called by hand from
// OnStartup and was exported, so it was also bound to the frontend.
//
// It is the one thing in this package that names the Wails application,
// and it is behind a build tag for the reason
// backend/events/runtime_wails.go states: cmd/indexbuild imports this
// package and is built without cgo, GTK or WebKit. Nothing under the
// indexbuild tag runs a Wails app, so the hook — and the context it
// installs — is simply absent there.
func (e *Service) ServiceStartup(
ctx context.Context,
_ application.ServiceOptions,
) error {
e.ctx = ctx
e.index.SetContext(ctx)
return nil
}