From b423f90e29949382c13c714dab621872214bf811 Mon Sep 17 00:00:00 2001 From: Alex Dunmow Date: Fri, 3 Jul 2026 18:34:28 +0800 Subject: [PATCH] feat: no -buildmode=plugin check (disabled until WO-WZ-017) Adds check 29: ported plugin repos must not carry a `-buildmode=plugin` build target (the legacy .so compile the wasm migration retires). Lands DISABLED behind a per-check const toggle (enableBuildmodePluginCheck=false) so it emits no output and never fails while the fleet is still porting; WO-WZ-017 flips it on. Scans plugin Makefiles/*.sh only. Unit-tested; golden snapshots unaffected. Co-Authored-By: Claude Fable 5 --- buildmodeplugin.go | 70 ++++++++++++++++++++++++++++++++++++++++ buildmodeplugin_test.go | 40 +++++++++++++++++++++++ check_buildmodeplugin.go | 39 ++++++++++++++++++++++ main.go | 1 + registry_test.go | 2 +- 5 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 buildmodeplugin.go create mode 100644 buildmodeplugin_test.go create mode 100644 check_buildmodeplugin.go diff --git a/buildmodeplugin.go b/buildmodeplugin.go new file mode 100644 index 0000000..409b842 --- /dev/null +++ b/buildmodeplugin.go @@ -0,0 +1,70 @@ +package main + +import ( + "os" + "path/filepath" + "strings" +) + +// buildmodePluginViolation is one occurrence of a `-buildmode=plugin` build +// target inside a plugin repo — the legacy `.so` compile that the wasm +// migration retires (WO-WZ-009 lands this check; WO-WZ-017 activates it). +type buildmodePluginViolation struct { + file string + line int + snippet string +} + +// buildmodePluginScanFiles names the build-orchestration files that legitimately +// carry a `go build` invocation; we scan only these to avoid flagging docs or +// generated Go that merely mentions the string. +func buildmodePluginScanFiles(name string) bool { + base := filepath.Base(name) + switch base { + case "Makefile", "makefile", "GNUmakefile": + return true + } + return strings.HasSuffix(base, ".sh") +} + +// checkBuildmodePlugin walks each plugin root's build files for a +// `-buildmode=plugin` occurrence. A ported plugin builds wasm via +// `ninja plugin build`; a lingering `.so` target means the port is incomplete. +func checkBuildmodePlugin(roots []pluginScanTarget) []buildmodePluginViolation { + var out []buildmodePluginViolation + for _, target := range roots { + _ = filepath.WalkDir(target.root, func(path string, d os.DirEntry, err error) error { + if err != nil { + return nil //nolint:nilerr // unreadable path is not a violation + } + if d.IsDir() { + if d.Name() == ".git" || d.Name() == "node_modules" || d.Name() == "vendor" { + return filepath.SkipDir + } + return nil + } + if !buildmodePluginScanFiles(path) { + return nil + } + data, readErr := os.ReadFile(path) + if readErr != nil { + return nil + } + for i, line := range strings.Split(string(data), "\n") { + if strings.Contains(line, "-buildmode=plugin") { + rel := path + if r, e := filepath.Rel(target.root, path); e == nil { + rel = filepath.Join(target.display, r) + } + out = append(out, buildmodePluginViolation{ + file: rel, + line: i + 1, + snippet: strings.TrimSpace(line), + }) + } + } + return nil + }) + } + return out +} diff --git a/buildmodeplugin_test.go b/buildmodeplugin_test.go new file mode 100644 index 0000000..921a1bc --- /dev/null +++ b/buildmodeplugin_test.go @@ -0,0 +1,40 @@ +package main + +import ( + "os" + "path/filepath" + "testing" +) + +func TestCheckBuildmodePlugin_FlagsMakefileAndScript(t *testing.T) { + root := t.TempDir() + if err := os.WriteFile(filepath.Join(root, "Makefile"), + []byte("build-so:\n\tgo build -buildmode=plugin -o plugin.so .\n"), 0o644); err != nil { + t.Fatal(err) + } + if err := os.WriteFile(filepath.Join(root, "build.sh"), + []byte("#!/bin/sh\ngo build -buildmode=plugin -o x.so .\n"), 0o644); err != nil { + t.Fatal(err) + } + // A non-build file mentioning the string must NOT be flagged. + if err := os.WriteFile(filepath.Join(root, "README.md"), + []byte("we used to run go build -buildmode=plugin here\n"), 0o644); err != nil { + t.Fatal(err) + } + + v := checkBuildmodePlugin([]pluginScanTarget{{root: root, display: "demo"}}) + if len(v) != 2 { + t.Fatalf("got %d violations, want 2: %+v", len(v), v) + } +} + +func TestCheckBuildmodePlugin_CleanRepo(t *testing.T) { + root := t.TempDir() + if err := os.WriteFile(filepath.Join(root, "Makefile"), + []byte("build-wasm:\n\tninja plugin build\n"), 0o644); err != nil { + t.Fatal(err) + } + if v := checkBuildmodePlugin([]pluginScanTarget{{root: root, display: "demo"}}); len(v) != 0 { + t.Fatalf("clean repo flagged: %+v", v) + } +} diff --git a/check_buildmodeplugin.go b/check_buildmodeplugin.go new file mode 100644 index 0000000..fd293b9 --- /dev/null +++ b/check_buildmodeplugin.go @@ -0,0 +1,39 @@ +package main + +import "fmt" + +// enableBuildmodePluginCheck is the per-check enable toggle. It ships DISABLED +// (WO-WZ-009): the wasm plugin migration is still porting the fleet off `.so`, +// so a `-buildmode=plugin` target is not yet a hard error. WO-WZ-017 flips this +// to true once every plugin repo builds wasm via `ninja plugin build`. +// +// While disabled the check prints NOTHING and never fails, so it neither +// disturbs golden snapshots nor blocks in-flight ports. +const enableBuildmodePluginCheck = false + +func init() { + register(Check{ + Seq: 290, + ID: "29", + Title: "No -buildmode=plugin targets in ported plugin repos", + Run: func(ctx *ScanContext, rep *Reporter) { + if !enableBuildmodePluginCheck { + return // disabled until WO-WZ-017; emit no output + } + fmt.Println("=== Check 29: No -buildmode=plugin targets in ported plugin repos ===") + violations := checkBuildmodePlugin(ctx.resolvedPluginTargets) + if len(violations) > 0 { + fmt.Printf(" FAIL: %d legacy .so build target(s) found:\n", len(violations)) + for _, v := range violations { + fmt.Printf(" %s:%d %s\n", v.file, v.line, v.snippet) + } + fmt.Println("\n Fix: build wasm with `ninja plugin build` (make build-wasm); drop the -buildmode=plugin target.") + rep.Fail() + } else { + fmt.Println(" OK: No -buildmode=plugin targets found") + printPerTargetOKLines(pluginTargetLabels(ctx.resolvedPluginTargets)) + } + fmt.Println() + }, + }) +} diff --git a/main.go b/main.go index 5f89d86..4d83863 100644 --- a/main.go +++ b/main.go @@ -34,6 +34,7 @@ // 26. Orchestrator backend tests must pass when checking the core BlockNinja repo // 27. No hand-rolled HTML sanitization — use bluemonday // 28. Public page handlers inject admin toolbar data +// 29. No -buildmode=plugin targets in ported plugin repos (disabled until WO-WZ-017) package main import ( diff --git a/registry_test.go b/registry_test.go index 01d0c5b..057bd73 100644 --- a/registry_test.go +++ b/registry_test.go @@ -18,7 +18,7 @@ func TestRegistryOrder(t *testing.T) { "1", "2", "2b", "2c", "2d", "2e", "2f", "3", "3b", "4", "5", "6", "7", "8", "9", "10", "10b", "10disc", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", - "28", + "28", "29", } ordered := make([]Check, len(registry))