Skip to content

Commit 0e9ed3d

Browse files
adonovangopherbot
authored andcommitted
go/packages: do not mutate Config
This CL establishes the documented (but previously false) invariant that Load does not mutate Config. The stateful fields have been moved: - gocmdRunner is moved to golistState; - goListOverlayFile is now a parameter of driver. In principle a non-standard driver might use this field (as suggested by the Config.Overlay documentation) but today none does. Nonetheless WriteOverlays is now called before the external driver. Fixes golang/go#67702 Change-Id: Ifc8a5228aecc6bcb6240ac82e2be3a514f03637e Reviewed-on: https://go-review.googlesource.com/c/tools/+/625475 Auto-Submit: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]>
1 parent ca2b41b commit 0e9ed3d

File tree

3 files changed

+38
-43
lines changed

3 files changed

+38
-43
lines changed

go/packages/external.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ type DriverResponse struct {
7979

8080
// driver is the type for functions that query the build system for the
8181
// packages named by the patterns.
82-
type driver func(cfg *Config, patterns ...string) (*DriverResponse, error)
82+
type driver func(cfg *Config, patterns []string) (*DriverResponse, error)
8383

8484
// findExternalDriver returns the file path of a tool that supplies
8585
// the build system package structure, or "" if not found.
@@ -103,7 +103,7 @@ func findExternalDriver(cfg *Config) driver {
103103
return nil
104104
}
105105
}
106-
return func(cfg *Config, words ...string) (*DriverResponse, error) {
106+
return func(cfg *Config, patterns []string) (*DriverResponse, error) {
107107
req, err := json.Marshal(DriverRequest{
108108
Mode: cfg.Mode,
109109
Env: cfg.Env,
@@ -117,7 +117,7 @@ func findExternalDriver(cfg *Config) driver {
117117

118118
buf := new(bytes.Buffer)
119119
stderr := new(bytes.Buffer)
120-
cmd := exec.CommandContext(cfg.Context, tool, words...)
120+
cmd := exec.CommandContext(cfg.Context, tool, patterns...)
121121
cmd.Dir = cfg.Dir
122122
// The cwd gets resolved to the real path. On Darwin, where
123123
// /tmp is a symlink, this breaks anything that expects the

go/packages/golist.go

+17-9
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ type golistState struct {
8080
cfg *Config
8181
ctx context.Context
8282

83+
runner *gocommand.Runner
84+
85+
// overlay is the JSON file that encodes the Config.Overlay
86+
// mapping, used by 'go list -overlay=...'.
87+
overlay string
88+
8389
envOnce sync.Once
8490
goEnvError error
8591
goEnv map[string]string
@@ -127,7 +133,10 @@ func (state *golistState) mustGetEnv() map[string]string {
127133
// goListDriver uses the go list command to interpret the patterns and produce
128134
// the build system package structure.
129135
// See driver for more details.
130-
func goListDriver(cfg *Config, patterns ...string) (_ *DriverResponse, err error) {
136+
//
137+
// overlay is the JSON file that encodes the cfg.Overlay
138+
// mapping, used by 'go list -overlay=...'
139+
func goListDriver(cfg *Config, runner *gocommand.Runner, overlay string, patterns []string) (_ *DriverResponse, err error) {
131140
// Make sure that any asynchronous go commands are killed when we return.
132141
parentCtx := cfg.Context
133142
if parentCtx == nil {
@@ -142,13 +151,15 @@ func goListDriver(cfg *Config, patterns ...string) (_ *DriverResponse, err error
142151
cfg: cfg,
143152
ctx: ctx,
144153
vendorDirs: map[string]bool{},
154+
overlay: overlay,
155+
runner: runner,
145156
}
146157

147158
// Fill in response.Sizes asynchronously if necessary.
148159
if cfg.Mode&NeedTypesSizes != 0 || cfg.Mode&(NeedTypes|NeedTypesInfo) != 0 {
149160
errCh := make(chan error)
150161
go func() {
151-
compiler, arch, err := getSizesForArgs(ctx, state.cfgInvocation(), cfg.gocmdRunner)
162+
compiler, arch, err := getSizesForArgs(ctx, state.cfgInvocation(), runner)
152163
response.dr.Compiler = compiler
153164
response.dr.Arch = arch
154165
errCh <- err
@@ -681,7 +692,7 @@ func (state *golistState) shouldAddFilenameFromError(p *jsonPackage) bool {
681692
// getGoVersion returns the effective minor version of the go command.
682693
func (state *golistState) getGoVersion() (int, error) {
683694
state.goVersionOnce.Do(func() {
684-
state.goVersion, state.goVersionError = gocommand.GoVersion(state.ctx, state.cfgInvocation(), state.cfg.gocmdRunner)
695+
state.goVersion, state.goVersionError = gocommand.GoVersion(state.ctx, state.cfgInvocation(), state.runner)
685696
})
686697
return state.goVersion, state.goVersionError
687698
}
@@ -840,7 +851,7 @@ func (state *golistState) cfgInvocation() gocommand.Invocation {
840851
Env: cfg.Env,
841852
Logf: cfg.Logf,
842853
WorkingDir: cfg.Dir,
843-
Overlay: cfg.goListOverlayFile,
854+
Overlay: state.overlay,
844855
}
845856
}
846857

@@ -851,11 +862,8 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer,
851862
inv := state.cfgInvocation()
852863
inv.Verb = verb
853864
inv.Args = args
854-
gocmdRunner := cfg.gocmdRunner
855-
if gocmdRunner == nil {
856-
gocmdRunner = &gocommand.Runner{}
857-
}
858-
stdout, stderr, friendlyErr, err := gocmdRunner.RunRaw(cfg.Context, inv)
865+
866+
stdout, stderr, friendlyErr, err := state.runner.RunRaw(cfg.Context, inv)
859867
if err != nil {
860868
// Check for 'go' executable not being found.
861869
if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {

go/packages/packages.go

+18-31
Original file line numberDiff line numberDiff line change
@@ -144,13 +144,7 @@ const (
144144
// A Config specifies details about how packages should be loaded.
145145
// The zero value is a valid configuration.
146146
//
147-
// Calls to Load do not modify this struct.
148-
//
149-
// TODO(adonovan): #67702: this is currently false: in fact,
150-
// calls to [Load] do not modify the public fields of this struct, but
151-
// may modify hidden fields, so concurrent calls to [Load] must not
152-
// use the same Config. But perhaps we should reestablish the
153-
// documented invariant.
147+
// Calls to [Load] do not modify this struct.
154148
type Config struct {
155149
// Mode controls the level of information returned for each package.
156150
Mode LoadMode
@@ -181,19 +175,10 @@ type Config struct {
181175
//
182176
Env []string
183177

184-
// gocmdRunner guards go command calls from concurrency errors.
185-
gocmdRunner *gocommand.Runner
186-
187178
// BuildFlags is a list of command-line flags to be passed through to
188179
// the build system's query tool.
189180
BuildFlags []string
190181

191-
// modFile will be used for -modfile in go command invocations.
192-
modFile string
193-
194-
// modFlag will be used for -modfile in go command invocations.
195-
modFlag string
196-
197182
// Fset provides source position information for syntax trees and types.
198183
// If Fset is nil, Load will use a new fileset, but preserve Fset's value.
199184
Fset *token.FileSet
@@ -240,9 +225,13 @@ type Config struct {
240225
// drivers may vary in their level of support for overlays.
241226
Overlay map[string][]byte
242227

243-
// goListOverlayFile is the JSON file that encodes the Overlay
244-
// mapping, used by 'go list -overlay=...'
245-
goListOverlayFile string
228+
// -- Hidden configuration fields only for use in x/tools --
229+
230+
// modFile will be used for -modfile in go command invocations.
231+
modFile string
232+
233+
// modFlag will be used for -modfile in go command invocations.
234+
modFlag string
246235
}
247236

248237
// Load loads and returns the Go packages named by the given patterns.
@@ -333,21 +322,24 @@ func defaultDriver(cfg *Config, patterns ...string) (*DriverResponse, bool, erro
333322
} else if !response.NotHandled {
334323
return response, true, nil
335324
}
336-
// (fall through)
325+
// not handled: fall through
337326
}
338327

339328
// go list fallback
340-
//
329+
341330
// Write overlays once, as there are many calls
342331
// to 'go list' (one per chunk plus others too).
343-
overlay, cleanupOverlay, err := gocommand.WriteOverlays(cfg.Overlay)
332+
overlayFile, cleanupOverlay, err := gocommand.WriteOverlays(cfg.Overlay)
344333
if err != nil {
345334
return nil, false, err
346335
}
347336
defer cleanupOverlay()
348-
cfg.goListOverlayFile = overlay
349337

350-
response, err := callDriverOnChunks(goListDriver, cfg, chunks)
338+
var runner gocommand.Runner // (shared across many 'go list' calls)
339+
driver := func(cfg *Config, patterns []string) (*DriverResponse, error) {
340+
return goListDriver(cfg, &runner, overlayFile, patterns)
341+
}
342+
response, err := callDriverOnChunks(driver, cfg, chunks)
351343
if err != nil {
352344
return nil, false, err
353345
}
@@ -385,16 +377,14 @@ func splitIntoChunks(patterns []string, argMax int) ([][]string, error) {
385377

386378
func callDriverOnChunks(driver driver, cfg *Config, chunks [][]string) (*DriverResponse, error) {
387379
if len(chunks) == 0 {
388-
return driver(cfg)
380+
return driver(cfg, nil)
389381
}
390382
responses := make([]*DriverResponse, len(chunks))
391383
errNotHandled := errors.New("driver returned NotHandled")
392384
var g errgroup.Group
393385
for i, chunk := range chunks {
394-
i := i
395-
chunk := chunk
396386
g.Go(func() (err error) {
397-
responses[i], err = driver(cfg, chunk...)
387+
responses[i], err = driver(cfg, chunk)
398388
if responses[i] != nil && responses[i].NotHandled {
399389
err = errNotHandled
400390
}
@@ -749,9 +739,6 @@ func newLoader(cfg *Config) *loader {
749739
if ld.Config.Env == nil {
750740
ld.Config.Env = os.Environ()
751741
}
752-
if ld.Config.gocmdRunner == nil {
753-
ld.Config.gocmdRunner = &gocommand.Runner{}
754-
}
755742
if ld.Context == nil {
756743
ld.Context = context.Background()
757744
}

0 commit comments

Comments
 (0)