Skip to content

Commit 442d20c

Browse files
committed
fix(protogen): refresh embedded tool metadata
Dependency setup treated any existing tools directory as current. Binaries rebuilt after a Common upgrade could therefore use an older extracted tools module, silently selecting stale protobuf and StarPC generator versions even though the current Common embed carried newer pins. Stamp the tools directory with the exact Common package identity used for extraction. Refresh embedded metadata when that identity changes or no stamp exists, preserve cached binaries, and advance the stamp only after extraction succeeds. A matching stamp remains a fast no-op, and the target module files remain untouched. Signed-off-by: Christian Stewart <christian@aperture.us>
1 parent caeb630 commit 442d20c

2 files changed

Lines changed: 69 additions & 21 deletions

File tree

‎cmd/aptre/cmd-deps.go‎

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -236,31 +236,43 @@ func ensureAllDeps(projectDir, toolsDir string, verbose, force bool) error {
236236
return nil
237237
}
238238

239-
func ensureToolsDir(projectDir, toolsPath string, verbose bool) error {
240-
if _, err := os.Stat(toolsPath); err == nil {
241-
return nil // Already exists
242-
}
239+
func toolsStampPath(toolsPath string) string {
240+
return filepath.Join(toolsPath, ".common-tools-stamp")
241+
}
243242

244-
if verbose {
245-
fmt.Println("Setting up tools directory...")
243+
func reconcileToolsStamp(stampPath, identity string, extract func() error) (bool, error) {
244+
if data, err := os.ReadFile(stampPath); err == nil && strings.TrimSpace(string(data)) == identity {
245+
return false, nil
246246
}
247-
248-
relToolsPath, err := filepath.Rel(projectDir, toolsPath)
249-
if err != nil {
250-
relToolsPath = filepath.Base(toolsPath)
247+
if err := extract(); err != nil {
248+
return false, err
249+
}
250+
if err := os.WriteFile(stampPath, []byte(identity+"\n"), 0o644); err != nil {
251+
return false, err
251252
}
253+
return true, nil
254+
}
252255

253-
commonPackage := resolveCommonPackage(projectDir)
254-
// Extracting the tools directory builds common's main package, which imports
255-
// C++ embed packages (abseil-cpp, protobuf) that the caller's module may lack
256-
// go.sum entries for. Pass -mod=mod so setup resolves them under a readonly
257-
// ambient default (CI, direct binary) instead of relying on the go:aptre
258-
// wrapper to set GOFLAGS.
259-
cmd := exec.Command("go", "run", "-mod=mod", "-v", commonPackage, relToolsPath)
260-
cmd.Dir = projectDir
261-
cmd.Stdout = os.Stdout
262-
cmd.Stderr = os.Stderr
263-
return cmd.Run()
256+
func ensureToolsDir(projectDir, toolsPath string, verbose bool) error {
257+
if err := os.MkdirAll(toolsPath, 0o755); err != nil {
258+
return err
259+
}
260+
identity := resolveCommonPackage(projectDir)
261+
_, err := reconcileToolsStamp(toolsStampPath(toolsPath), identity, func() error {
262+
if verbose {
263+
fmt.Println("Synchronizing embedded tool metadata...")
264+
}
265+
relToolsPath, err := filepath.Rel(projectDir, toolsPath)
266+
if err != nil {
267+
relToolsPath = filepath.Base(toolsPath)
268+
}
269+
cmd := exec.Command("go", "run", "-mod=mod", "-v", identity, relToolsPath)
270+
cmd.Dir = projectDir
271+
cmd.Stdout = os.Stdout
272+
cmd.Stderr = os.Stderr
273+
return cmd.Run()
274+
})
275+
return err
264276
}
265277

266278
func resolveCommonPackage(projectDir string) string {

‎cmd/aptre/cmd-deps_test.go‎

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import (
77
"testing"
88

99
"github.com/aperturerobotics/common/protogen"
10+
"github.com/pkg/errors"
1011
)
1112

1213
func TestPlanGenerateDependenciesByLanguageAndRPC(t *testing.T) {
@@ -77,3 +78,38 @@ func TestSelectedToolPlanBranches(t *testing.T) {
7778
t.Fatalf("unselected product mode=%q", got.mode)
7879
}
7980
}
81+
82+
func TestReconcileToolsStampLifecycle(t *testing.T) {
83+
path := filepath.Join(t.TempDir(), ".common-tools-stamp")
84+
calls := 0
85+
extract := func() error { calls++; return nil }
86+
changed, err := reconcileToolsStamp(path, "common@v1", extract)
87+
if err != nil || !changed || calls != 1 {
88+
t.Fatalf("first %v %v %d", changed, err, calls)
89+
}
90+
changed, err = reconcileToolsStamp(path, "common@v1", extract)
91+
if err != nil || changed || calls != 1 {
92+
t.Fatalf("noop %v %v %d", changed, err, calls)
93+
}
94+
changed, err = reconcileToolsStamp(path, "common@v2", extract)
95+
if err != nil || !changed || calls != 2 {
96+
t.Fatalf("change %v %v %d", changed, err, calls)
97+
}
98+
os.Remove(path)
99+
changed, err = reconcileToolsStamp(path, "common@v3", extract)
100+
if err != nil || !changed || calls != 3 {
101+
t.Fatalf("missing %v %v %d", changed, err, calls)
102+
}
103+
}
104+
func TestReconcileToolsStampFailedExtractionDoesNotAdvance(t *testing.T) {
105+
path := filepath.Join(t.TempDir(), "stamp")
106+
os.WriteFile(path, []byte("common@old\n"), 0o644)
107+
_, err := reconcileToolsStamp(path, "common@new", func() error { return errors.New("failed") })
108+
if err == nil {
109+
t.Fatal("expected error")
110+
}
111+
got, _ := os.ReadFile(path)
112+
if string(got) != "common@old\n" {
113+
t.Fatalf("stamp advanced: %q", got)
114+
}
115+
}

0 commit comments

Comments
 (0)