Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
e6a9903
feat: incremental loading
cmilesdev Mar 14, 2026
e3f07cb
feat(incremental): reuse unchanged local packages in fast path and ha…
cmilesdev Mar 14, 2026
2eb5400
perf(incremental): trim cold bootstrap work and keep warm shape chang…
cmilesdev Mar 14, 2026
ad2d561
perf(incremental): load deps conditionally
cmilesdev Mar 14, 2026
578b24f
chore(incremental): clear session cache
cmilesdev Mar 14, 2026
83806b9
fix(cli): improve wire error coloring and solve error labeling
cmilesdev Mar 14, 2026
c6e4f4e
feat(incremental): harden loader and scenario tooling
cmilesdev Mar 15, 2026
d3486f5
feat: custom loader initial
cmilesdev Mar 15, 2026
e7cfc63
feat: external loader caching
cmilesdev Mar 15, 2026
9379659
chore: remove local caching strat
cmilesdev Mar 15, 2026
3bc75c4
feat: local caching from wire perspective
cmilesdev Mar 15, 2026
a8c2a02
feat: go dep cache
cmilesdev Mar 15, 2026
26f591b
feat(loader): cache unchanged root output
cmilesdev Mar 15, 2026
a357a38
chore: bench tweaks
cmilesdev Mar 15, 2026
d6b36b1
chore: re-implement cache
cmilesdev Mar 15, 2026
84087dd
fix: provider discovery
cmilesdev Mar 15, 2026
e968dcc
chore: benchmark update
cmilesdev Mar 16, 2026
ba95466
fix: ci
cmilesdev Mar 16, 2026
4b312b5
fix: ci
cmilesdev Mar 16, 2026
433ffc7
fix: windows tmpdir issue
cmilesdev Mar 16, 2026
d941179
fix: windows bench executable path
cmilesdev Mar 16, 2026
88c8634
fix(loader): strengthen artifact keys for replaced external modules
cmilesdev Mar 16, 2026
09073ee
test(loader): harden cache invalidation and discovery parity coverage
cmilesdev Mar 16, 2026
fea5e0a
fix(loader): treat replaced workspace deps as local and harden runtests
cmilesdev Mar 16, 2026
568d2e0
fix(loader): make cache-hardening tests and runtests portable
cmilesdev Mar 16, 2026
114d174
fix(loader): use valid file GOPROXY URLs in proxy-based tests
cmilesdev Mar 16, 2026
53890d7
fix(loader): format file GOPROXY URLs correctly on windows
cmilesdev Mar 16, 2026
efa0214
fix(loader): normalize test path comparisons across platforms
cmilesdev Mar 16, 2026
541acdf
refactor: remove unused loader and wire helpers
cmilesdev Mar 16, 2026
40ab144
refactor: dedupe command and custom loader helpers
cmilesdev Mar 16, 2026
0921843
refactor: make loader artifact policy explicit
cmilesdev Mar 16, 2026
bb7af77
refactor: dedupe custom loader import linking
cmilesdev Mar 16, 2026
df22b6a
refactor: share import target resolution in custom loader
cmilesdev Mar 16, 2026
a857e0b
refactor: centralize types info setup in custom loader
cmilesdev Mar 16, 2026
11b5498
refactor: share parse error conversion in custom loader
cmilesdev Mar 16, 2026
7bd7c65
refactor: share source parsing in custom loader
cmilesdev Mar 16, 2026
6e3fed5
refactor: centralize semantic artifact cache inputs
cmilesdev Mar 16, 2026
c2abf4e
refactor: isolate semantic artifact cache io
cmilesdev Mar 16, 2026
40ea02b
refactor: isolate semantic provider set artifact lookup
cmilesdev Mar 16, 2026
5f29565
refactor: extract semantic provider set item application
cmilesdev Mar 16, 2026
91845b1
refactor: share semantic struct field helpers
cmilesdev Mar 16, 2026
9d894b2
refactor: share semantic package object lookup
cmilesdev Mar 16, 2026
3775c4c
refactor: share semantic output type assembly
cmilesdev Mar 17, 2026
2091c4b
refactor: share struct provider shell assembly
cmilesdev Mar 17, 2026
a7cd4cd
refactor: share allowed struct field inputs
cmilesdev Mar 17, 2026
051fb75
refactor: share selected struct field inputs
cmilesdev Mar 17, 2026
7f6195a
refactor: share field output assembly for FieldsOf
cmilesdev Mar 17, 2026
3264253
refactor: share quoted struct field lookup
cmilesdev Mar 17, 2026
41f4d7d
refactor: share semantic pointer expansion
cmilesdev Mar 17, 2026
dea5a63
refactor: reuse field parent struct resolution
cmilesdev Mar 17, 2026
0606067
refactor: share field object assembly
cmilesdev Mar 17, 2026
f9d735f
refactor: share named struct type resolution
cmilesdev Mar 17, 2026
4477c75
refactor: share semantic type name lookup
cmilesdev Mar 17, 2026
2c0446d
refactor: share semantic package member lookup
cmilesdev Mar 17, 2026
848371f
refactor: share semantic error wrapping
cmilesdev Mar 17, 2026
3944cbb
refactor: share provider set finalization
cmilesdev Mar 17, 2026
12858a2
refactor: add isolated output cache gate
cmilesdev Mar 17, 2026
8dbd590
refactor: make provider set fallback policy explicit
cmilesdev Mar 17, 2026
9ef6b11
refactor: share custom loader root loading path
cmilesdev Mar 17, 2026
4386089
refactor: share custom loader metadata root graph
cmilesdev Mar 17, 2026
b099db8
refactor: isolate semantic provider set support rule
cmilesdev Mar 17, 2026
cf4bb80
refactor: fold back weak cleanup abstractions
cmilesdev Mar 17, 2026
c323a0f
refactor: narrow loader semantic artifact coupling
cmilesdev Mar 17, 2026
7bf31e8
refactor: unify semantic provider set support rules
cmilesdev Mar 17, 2026
a7fc49c
fix: restore local loader artifact safety gate
cmilesdev Mar 17, 2026
df1f6f6
refactor: disable semantic reconstruction by default
cmilesdev Mar 17, 2026
3927014
refactor: remove semantic reconstruction path
cmilesdev Mar 17, 2026
4af9edc
refactor: remove semantic cache layer
cmilesdev Mar 17, 2026
bf14bb5
refactor: add import benchmark profile filter
cmilesdev Mar 17, 2026
bf4a02d
refactor: trim redundant discovery cache metadata
cmilesdev Mar 17, 2026
788798d
refactor: remove redundant discovery cache cloning
cmilesdev Mar 17, 2026
0921fc2
refactor: split external benchmark profiles
cmilesdev Mar 17, 2026
e52201b
refactor: share custom typed load pipeline
cmilesdev Mar 17, 2026
2dc4ac4
refactor: centralize custom metadata loading
cmilesdev Mar 17, 2026
6f09664
refactor: share loader fallback reason policy
cmilesdev Mar 17, 2026
d003c9f
refactor: add targeted local profile benchmark
cmilesdev Mar 17, 2026
a112565
refactor: add one-shot import profile harness
cmilesdev Mar 17, 2026
ee3ffc9
perf: reuse root discovery for generate loads
cmilesdev Mar 17, 2026
cf52879
style: format loader discovery changes
cmilesdev Mar 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 131 additions & 15 deletions cmd/wire/cache_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,46 +19,162 @@ import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"sort"
"strings"

"github.com/goforj/wire/internal/wire"
"github.com/google/subcommands"
)

const (
loaderArtifactDirEnv = "WIRE_LOADER_ARTIFACT_DIR"
outputCacheDirEnv = "WIRE_OUTPUT_CACHE_DIR"
semanticCacheDirEnv = "WIRE_SEMANTIC_CACHE_DIR"
)

var osUserCacheDir = os.UserCacheDir

type cacheCmd struct {
clear bool
}

// Name returns the subcommand name.
type cacheTarget struct {
name string
path string
}

func (*cacheCmd) Name() string { return "cache" }

// Synopsis returns a short summary of the subcommand.
func (*cacheCmd) Synopsis() string {
return "inspect or clear the wire cache"
}

// Usage returns the help text for the subcommand.
func (*cacheCmd) Usage() string {
return `cache [-clear]
return `cache
cache clear
cache -clear

By default, prints the cache directory. With -clear, removes all cache files.
By default, prints the cache directory. With -clear or clear, removes all
Wire-managed cache files.
`
}

// SetFlags registers flags for the subcommand.
func (cmd *cacheCmd) SetFlags(f *flag.FlagSet) {
f.BoolVar(&cmd.clear, "clear", false, "remove all cached data")
f.BoolVar(&cmd.clear, "clear", false, "clear Wire caches")
}

// Execute runs the subcommand.
func (cmd *cacheCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
if cmd.clear {
if err := wire.ClearCache(); err != nil {
log.Printf("failed to clear cache: %v\n", err)
return subcommands.ExitFailure
_ = ctx
clearRequested := cmd.clear
switch extra := f.Args(); len(extra) {
case 0:
if !clearRequested {
root, err := wireCacheRoot(os.Environ())
if err != nil {
log.Println(err)
return subcommands.ExitFailure
}
fmt.Fprintln(os.Stdout, root)
return subcommands.ExitSuccess
}
case 1:
if extra[0] == "clear" {
clearRequested = true
break
}
log.Printf("cleared cache at %s\n", wire.CacheDir())
log.Printf("unknown cache action %q", extra[0])
log.Println(strings.TrimSpace(cmd.Usage()))
return subcommands.ExitFailure
default:
log.Println(strings.TrimSpace(cmd.Usage()))
return subcommands.ExitFailure
}
if !clearRequested {
log.Println(strings.TrimSpace(cmd.Usage()))
return subcommands.ExitFailure
}
cleared, err := clearWireCaches(os.Environ())
if err != nil {
log.Printf("failed to clear cache: %v\n", err)
return subcommands.ExitFailure
}
root, err := wireCacheRoot(os.Environ())
if err != nil {
log.Println(err)
return subcommands.ExitFailure
}
if len(cleared) == 0 {
log.Printf("cleared cache at %s\n", root)
return subcommands.ExitSuccess
}
fmt.Println(wire.CacheDir())
log.Printf("cleared cache at %s\n", root)
return subcommands.ExitSuccess
}

func wireCacheRoot(env []string) (string, error) {
base, err := osUserCacheDir()
if err != nil {
return "", fmt.Errorf("resolve user cache dir: %w", err)
}
return filepath.Join(base, "wire"), nil
}

func clearWireCaches(env []string) ([]string, error) {
base, err := wireCacheRoot(env)
if err != nil {
return nil, err
}
targets := wireCacheTargets(env, filepath.Dir(base))
cleared := make([]string, 0, len(targets))
for _, target := range targets {
info, err := os.Stat(target.path)
if os.IsNotExist(err) {
continue
}
if err != nil {
return cleared, fmt.Errorf("stat %s cache: %w", target.name, err)
}
if !info.IsDir() {
if err := os.Remove(target.path); err != nil {
return cleared, fmt.Errorf("remove %s cache: %w", target.name, err)
}
} else if err := os.RemoveAll(target.path); err != nil {
return cleared, fmt.Errorf("remove %s cache: %w", target.name, err)
}
cleared = append(cleared, target.name)
}
return cleared, nil
}

func wireCacheTargets(env []string, userCacheDir string) []cacheTarget {
baseWire := filepath.Join(userCacheDir, "wire")
targets := []cacheTarget{
{name: "loader-artifacts", path: envValueDefault(env, loaderArtifactDirEnv, filepath.Join(baseWire, "loader-artifacts"))},
{name: "discovery-cache", path: filepath.Join(baseWire, "discovery-cache")},
{name: "output-cache", path: envValueDefault(env, outputCacheDirEnv, filepath.Join(baseWire, "output-cache"))},
}
seen := make(map[string]bool, len(targets))
deduped := make([]cacheTarget, 0, len(targets))
for _, target := range targets {
cleaned := filepath.Clean(target.path)
if seen[cleaned] {
continue
}
seen[cleaned] = true
target.path = cleaned
deduped = append(deduped, target)
}
sort.Slice(deduped, func(i, j int) bool { return deduped[i].name < deduped[j].name })
return deduped
}

func envValueDefault(env []string, key, fallback string) string {
for i := len(env) - 1; i >= 0; i-- {
parts := strings.SplitN(env[i], "=", 2)
if len(parts) == 2 && parts[0] == key && parts[1] != "" {
return parts[1]
}
}
return fallback
}
92 changes: 92 additions & 0 deletions cmd/wire/cache_cmd_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package main

import (
"os"
"path/filepath"
"testing"
)

func TestWireCacheTargetsDefault(t *testing.T) {
base := filepath.Join(t.TempDir(), "cache")
got := wireCacheTargets(nil, base)
want := map[string]string{
"discovery-cache": filepath.Join(base, "wire", "discovery-cache"),
"loader-artifacts": filepath.Join(base, "wire", "loader-artifacts"),
"output-cache": filepath.Join(base, "wire", "output-cache"),
}
if len(got) != len(want) {
t.Fatalf("targets len = %d, want %d", len(got), len(want))
}
for _, target := range got {
if target.path != want[target.name] {
t.Fatalf("%s path = %q, want %q", target.name, target.path, want[target.name])
}
}
}

func TestWireCacheRoot(t *testing.T) {
base := filepath.Join(t.TempDir(), "cache")
old := osUserCacheDir
osUserCacheDir = func() (string, error) { return base, nil }
defer func() { osUserCacheDir = old }()

got, err := wireCacheRoot(nil)
if err != nil {
t.Fatalf("wireCacheRoot() error = %v", err)
}
want := filepath.Join(base, "wire")
if got != want {
t.Fatalf("wireCacheRoot() = %q, want %q", got, want)
}
}

func TestWireCacheTargetsRespectOverrides(t *testing.T) {
base := filepath.Join(t.TempDir(), "cache")
env := []string{
loaderArtifactDirEnv + "=" + filepath.Join(base, "loader"),
outputCacheDirEnv + "=" + filepath.Join(base, "output"),
}
got := wireCacheTargets(env, base)
want := map[string]string{
"discovery-cache": filepath.Join(base, "wire", "discovery-cache"),
"loader-artifacts": filepath.Join(base, "loader"),
"output-cache": filepath.Join(base, "output"),
}
for _, target := range got {
if target.path != want[target.name] {
t.Fatalf("%s path = %q, want %q", target.name, target.path, want[target.name])
}
}
}

func TestClearWireCachesRemovesTargets(t *testing.T) {
base := filepath.Join(t.TempDir(), "cache")
env := []string{
loaderArtifactDirEnv + "=" + filepath.Join(base, "loader"),
outputCacheDirEnv + "=" + filepath.Join(base, "output"),
}
for _, target := range wireCacheTargets(env, base) {
if err := os.MkdirAll(target.path, 0o755); err != nil {
t.Fatalf("MkdirAll(%q): %v", target.path, err)
}
if err := os.WriteFile(filepath.Join(target.path, "marker"), []byte("x"), 0o644); err != nil {
t.Fatalf("WriteFile(%q): %v", target.path, err)
}
}
old := osUserCacheDir
osUserCacheDir = func() (string, error) { return base, nil }
defer func() { osUserCacheDir = old }()

cleared, err := clearWireCaches(env)
if err != nil {
t.Fatalf("clearWireCaches() error = %v", err)
}
if len(cleared) != 3 {
t.Fatalf("cleared len = %d, want 3", len(cleared))
}
for _, target := range wireCacheTargets(env, base) {
if _, err := os.Stat(target.path); !os.IsNotExist(err) {
t.Fatalf("%s still exists after clear, stat err = %v", target.path, err)
}
}
}
39 changes: 1 addition & 38 deletions cmd/wire/gen_cmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,7 @@ import (
"flag"
"log"
"os"
"time"

"github.com/goforj/wire/internal/wire"
"github.com/google/subcommands"
)

Expand Down Expand Up @@ -66,7 +64,6 @@ func (cmd *genCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
return subcommands.ExitFailure
}
defer stop()
totalStart := time.Now()
ctx = withTiming(ctx, cmd.profile.timings)

wd, err := os.Getwd()
Expand All @@ -83,42 +80,8 @@ func (cmd *genCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
opts.PrefixOutputFile = cmd.prefixFileName
opts.Tags = cmd.tags

genStart := time.Now()
outs, errs := wire.Generate(ctx, wd, os.Environ(), packages(f), opts)
logTiming(cmd.profile.timings, "wire.Generate", genStart)
if len(errs) > 0 {
logErrors(errs)
log.Println("generate failed")
if !runGenerateCommand(ctx, wd, os.Environ(), packages(f), opts, cmd.profile.timings) {
return subcommands.ExitFailure
}
if len(outs) == 0 {
logTiming(cmd.profile.timings, "total", totalStart)
return subcommands.ExitSuccess
}
success := true
writeStart := time.Now()
for _, out := range outs {
if len(out.Errs) > 0 {
logErrors(out.Errs)
log.Printf("%s: generate failed\n", out.PkgPath)
success = false
}
if len(out.Content) == 0 {
// No Wire output. Maybe errors, maybe no Wire directives.
continue
}
if err := out.Commit(); err == nil {
log.Printf("%s: wrote %s (%s)\n", out.PkgPath, out.OutputPath, formatDuration(time.Since(totalStart)))
} else {
log.Printf("%s: failed to write %s: %v\n", out.PkgPath, out.OutputPath, err)
success = false
}
}
if !success {
log.Println("at least one generate failure")
return subcommands.ExitFailure
}
logTiming(cmd.profile.timings, "writes", writeStart)
logTiming(cmd.profile.timings, "total", totalStart)
return subcommands.ExitSuccess
}
59 changes: 59 additions & 0 deletions cmd/wire/generate_runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package main

import (
"context"
"fmt"
"log"
"time"

"github.com/goforj/wire/internal/wire"
)

func runGenerateCommand(ctx context.Context, wd string, env []string, patterns []string, opts *wire.GenerateOptions, timings bool) bool {
totalStart := time.Now()
genStart := time.Now()
outs, errs := wire.Generate(ctx, wd, env, patterns, opts)
logTiming(timings, "wire.Generate", genStart)
if len(errs) > 0 {
logErrors(errs)
log.Println("generate failed")
return false
}
if len(outs) == 0 {
logTiming(timings, "total", totalStart)
return true
}
success := true
writeStart := time.Now()
for _, out := range outs {
if len(out.Errs) > 0 {
logErrors(out.Errs)
log.Printf("%s: generate failed\n", out.PkgPath)
success = false
}
if len(out.Content) == 0 {
continue
}
if wrote, err := out.CommitWithStatus(); err == nil {
if wrote {
logSuccessf("%s: wrote %s (%s)", out.PkgPath, out.OutputPath, formatDuration(time.Since(totalStart)))
} else {
logSuccessf("%s: unchanged %s (%s)", out.PkgPath, out.OutputPath, formatDuration(time.Since(totalStart)))
}
} else {
log.Printf("%s: failed to write %s: %v\n", out.PkgPath, out.OutputPath, err)
success = false
}
}
if !success {
log.Println("at least one generate failure")
return false
}
logTiming(timings, "writes", writeStart)
logTiming(timings, "total", totalStart)
return true
}

func formatDuration(d time.Duration) string {
return fmt.Sprintf("%.2fms", float64(d)/float64(time.Millisecond))
}
Loading
Loading