Skip to content

Commit 2f4fa18

Browse files
committed
go/packages: use native overlay support for 1.16
This change modifies go/packages to use the go command's -overlay flag if used with Go 1.16. It does so by adding a new Overlay field to the gocommand.Invocation struct. go/packages writes out the overlay files as expected by go list before invoking a `go list` command. Fixes golang/go#41598 Change-Id: Iec5edf19ce2936d5a633d076905622c2cf779bcc Reviewed-on: https://go-review.googlesource.com/c/tools/+/263984 Trust: Rebecca Stambler <[email protected]> Run-TryBot: Rebecca Stambler <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Heschi Kreinick <[email protected]>
1 parent ffe8bce commit 2f4fa18

File tree

3 files changed

+153
-46
lines changed

3 files changed

+153
-46
lines changed

go/packages/golist.go

Lines changed: 127 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"encoding/json"
1111
"fmt"
1212
"go/types"
13+
"io/ioutil"
1314
"log"
1415
"os"
1516
"os/exec"
@@ -208,56 +209,58 @@ extractQueries:
208209
}
209210
}
210211

211-
modifiedPkgs, needPkgs, err := state.processGolistOverlay(response)
212-
if err != nil {
213-
return nil, err
214-
}
212+
// Only use go/packages' overlay processing if we're using a Go version
213+
// below 1.16. Otherwise, go list handles it.
214+
if goVersion, err := state.getGoVersion(); err == nil && goVersion < 16 {
215+
modifiedPkgs, needPkgs, err := state.processGolistOverlay(response)
216+
if err != nil {
217+
return nil, err
218+
}
215219

216-
var containsCandidates []string
217-
if len(containFiles) > 0 {
218-
containsCandidates = append(containsCandidates, modifiedPkgs...)
219-
containsCandidates = append(containsCandidates, needPkgs...)
220-
}
221-
if err := state.addNeededOverlayPackages(response, needPkgs); err != nil {
222-
return nil, err
223-
}
224-
// Check candidate packages for containFiles.
225-
if len(containFiles) > 0 {
226-
for _, id := range containsCandidates {
227-
pkg, ok := response.seenPackages[id]
228-
if !ok {
229-
response.addPackage(&Package{
230-
ID: id,
231-
Errors: []Error{
232-
{
220+
var containsCandidates []string
221+
if len(containFiles) > 0 {
222+
containsCandidates = append(containsCandidates, modifiedPkgs...)
223+
containsCandidates = append(containsCandidates, needPkgs...)
224+
}
225+
if err := state.addNeededOverlayPackages(response, needPkgs); err != nil {
226+
return nil, err
227+
}
228+
// Check candidate packages for containFiles.
229+
if len(containFiles) > 0 {
230+
for _, id := range containsCandidates {
231+
pkg, ok := response.seenPackages[id]
232+
if !ok {
233+
response.addPackage(&Package{
234+
ID: id,
235+
Errors: []Error{{
233236
Kind: ListError,
234237
Msg: fmt.Sprintf("package %s expected but not seen", id),
235-
},
236-
},
237-
})
238-
continue
239-
}
240-
for _, f := range containFiles {
241-
for _, g := range pkg.GoFiles {
242-
if sameFile(f, g) {
243-
response.addRoot(id)
238+
}},
239+
})
240+
continue
241+
}
242+
for _, f := range containFiles {
243+
for _, g := range pkg.GoFiles {
244+
if sameFile(f, g) {
245+
response.addRoot(id)
246+
}
244247
}
245248
}
246249
}
247250
}
248-
}
249-
// Add root for any package that matches a pattern. This applies only to
250-
// packages that are modified by overlays, since they are not added as
251-
// roots automatically.
252-
for _, pattern := range restPatterns {
253-
match := matchPattern(pattern)
254-
for _, pkgID := range modifiedPkgs {
255-
pkg, ok := response.seenPackages[pkgID]
256-
if !ok {
257-
continue
258-
}
259-
if match(pkg.PkgPath) {
260-
response.addRoot(pkg.ID)
251+
// Add root for any package that matches a pattern. This applies only to
252+
// packages that are modified by overlays, since they are not added as
253+
// roots automatically.
254+
for _, pattern := range restPatterns {
255+
match := matchPattern(pattern)
256+
for _, pkgID := range modifiedPkgs {
257+
pkg, ok := response.seenPackages[pkgID]
258+
if !ok {
259+
continue
260+
}
261+
if match(pkg.PkgPath) {
262+
response.addRoot(pkg.ID)
263+
}
261264
}
262265
}
263266
}
@@ -835,6 +838,26 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer,
835838
cfg := state.cfg
836839

837840
inv := state.cfgInvocation()
841+
842+
// For Go versions 1.16 and above, `go list` accepts overlays directly via
843+
// the -overlay flag. Set it, if it's available.
844+
//
845+
// The check for "list" is not necessarily required, but we should avoid
846+
// getting the go version if possible.
847+
if verb == "list" {
848+
goVersion, err := state.getGoVersion()
849+
if err != nil {
850+
return nil, err
851+
}
852+
if goVersion >= 16 {
853+
filename, cleanup, err := state.writeOverlays()
854+
if err != nil {
855+
return nil, err
856+
}
857+
defer cleanup()
858+
inv.Overlay = filename
859+
}
860+
}
838861
inv.Verb = verb
839862
inv.Args = args
840863
gocmdRunner := cfg.gocmdRunner
@@ -976,6 +999,67 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer,
976999
return stdout, nil
9771000
}
9781001

1002+
// OverlayJSON is the format overlay files are expected to be in.
1003+
// The Replace map maps from overlaid paths to replacement paths:
1004+
// the Go command will forward all reads trying to open
1005+
// each overlaid path to its replacement path, or consider the overlaid
1006+
// path not to exist if the replacement path is empty.
1007+
//
1008+
// From golang/go#39958.
1009+
type OverlayJSON struct {
1010+
Replace map[string]string `json:"replace,omitempty"`
1011+
}
1012+
1013+
// writeOverlays writes out files for go list's -overlay flag, as described
1014+
// above.
1015+
func (state *golistState) writeOverlays() (filename string, cleanup func(), err error) {
1016+
// Do nothing if there are no overlays in the config.
1017+
if len(state.cfg.Overlay) == 0 {
1018+
return "", func() {}, nil
1019+
}
1020+
dir, err := ioutil.TempDir("", "gopackages-*")
1021+
if err != nil {
1022+
return "", nil, err
1023+
}
1024+
// The caller must clean up this directory, unless this function returns an
1025+
// error.
1026+
cleanup = func() {
1027+
os.RemoveAll(dir)
1028+
}
1029+
defer func() {
1030+
if err != nil {
1031+
cleanup()
1032+
}
1033+
}()
1034+
overlays := map[string]string{}
1035+
for k, v := range state.cfg.Overlay {
1036+
// Create a unique filename for the overlaid files, to avoid
1037+
// creating nested directories.
1038+
noSeparator := strings.Join(strings.Split(filepath.ToSlash(k), "/"), "")
1039+
f, err := ioutil.TempFile(dir, fmt.Sprintf("*-%s", noSeparator))
1040+
if err != nil {
1041+
return "", func() {}, err
1042+
}
1043+
if _, err := f.Write(v); err != nil {
1044+
return "", func() {}, err
1045+
}
1046+
if err := f.Close(); err != nil {
1047+
return "", func() {}, err
1048+
}
1049+
overlays[k] = f.Name()
1050+
}
1051+
b, err := json.Marshal(OverlayJSON{Replace: overlays})
1052+
if err != nil {
1053+
return "", func() {}, err
1054+
}
1055+
// Write out the overlay file that contains the filepath mappings.
1056+
filename = filepath.Join(dir, "overlay.json")
1057+
if err := ioutil.WriteFile(filename, b, 0665); err != nil {
1058+
return "", func() {}, err
1059+
}
1060+
return filename, cleanup, nil
1061+
}
1062+
9791063
func containsGoFile(s []string) bool {
9801064
for _, f := range s {
9811065
if strings.HasSuffix(f, ".go") {

go/packages/overlay_test.go

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,9 @@ func testOverlayChangesBothPackageNames(t *testing.T, exporter packagestest.Expo
8787
{"fake [fake.test]", "foox", 2},
8888
{"fake.test", "main", 1},
8989
}
90+
if len(initial) != 3 {
91+
t.Fatalf("expected 3 packages, got %v", len(initial))
92+
}
9093
for i := 0; i < 3; i++ {
9194
if ok := checkPkg(t, initial[i], want[i].id, want[i].name, want[i].count); !ok {
9295
t.Errorf("%d: got {%s %s %d}, expected %v", i, initial[i].ID,
@@ -102,7 +105,8 @@ func TestOverlayChangesTestPackageName(t *testing.T) {
102105
packagestest.TestAll(t, testOverlayChangesTestPackageName)
103106
}
104107
func testOverlayChangesTestPackageName(t *testing.T, exporter packagestest.Exporter) {
105-
log.SetFlags(log.Lshortfile)
108+
testenv.NeedsGo1Point(t, 16)
109+
106110
exported := packagestest.Export(t, exporter, []packagestest.Module{{
107111
Name: "fake",
108112
Files: map[string]interface{}{
@@ -127,10 +131,13 @@ func testOverlayChangesTestPackageName(t *testing.T, exporter packagestest.Expor
127131
id, name string
128132
count int
129133
}{
130-
{"fake", "foo", 0},
134+
{"fake", "foox", 0},
131135
{"fake [fake.test]", "foox", 1},
132136
{"fake.test", "main", 1},
133137
}
138+
if len(initial) != 3 {
139+
t.Fatalf("expected 3 packages, got %v", len(initial))
140+
}
134141
for i := 0; i < 3; i++ {
135142
if ok := checkPkg(t, initial[i], want[i].id, want[i].name, want[i].count); !ok {
136143
t.Errorf("got {%s %s %d}, expected %v", initial[i].ID,
@@ -329,6 +336,9 @@ func testOverlayDeps(t *testing.T, exporter packagestest.Exporter) {
329336

330337
// Find package golang.org/fake/c
331338
sort.Slice(pkgs, func(i, j int) bool { return pkgs[i].ID < pkgs[j].ID })
339+
if len(pkgs) != 2 {
340+
t.Fatalf("expected 2 packages, got %v", len(pkgs))
341+
}
332342
pkgc := pkgs[0]
333343
if pkgc.ID != "golang.org/fake/c" {
334344
t.Errorf("expected first package in sorted list to be \"golang.org/fake/c\", got %v", pkgc.ID)
@@ -804,6 +814,9 @@ func testInvalidFilesBeforeOverlayContains(t *testing.T, exporter packagestest.E
804814
if err != nil {
805815
t.Fatal(err)
806816
}
817+
if len(initial) != 1 {
818+
t.Fatalf("expected 1 packages, got %v", len(initial))
819+
}
807820
pkg := initial[0]
808821
if pkg.ID != tt.wantID {
809822
t.Fatalf("expected package ID %q, got %q", tt.wantID, pkg.ID)
@@ -986,7 +999,7 @@ func Hi() {
986999
}
9871000
}
9881001
if match == nil {
989-
t.Fatalf(`expected package path "golang.org/fake/a", got %q`, match.PkgPath)
1002+
t.Fatalf(`expected package path "golang.org/fake/a", got none`)
9901003
}
9911004
if match.PkgPath != "golang.org/fake/a" {
9921005
t.Fatalf(`expected package path "golang.org/fake/a", got %q`, match.PkgPath)
@@ -1072,6 +1085,9 @@ replace (
10721085
if err != nil {
10731086
t.Error(err)
10741087
}
1088+
if len(initial) != 1 {
1089+
t.Fatalf(`expected 1 package, got %v`, len(initial))
1090+
}
10751091
pkg := initial[0]
10761092
if pkg.PkgPath != "b.com/inner" {
10771093
t.Fatalf(`expected package path "b.com/inner", got %q`, pkg.PkgPath)

internal/gocommand/invoke.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ type Invocation struct {
132132
BuildFlags []string
133133
ModFlag string
134134
ModFile string
135+
Overlay string
135136
Env []string
136137
WorkingDir string
137138
Logf func(format string, args ...interface{})
@@ -171,6 +172,11 @@ func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error {
171172
goArgs = append(goArgs, "-mod="+i.ModFlag)
172173
}
173174
}
175+
appendOverlayFlag := func() {
176+
if i.Overlay != "" {
177+
goArgs = append(goArgs, "-overlay="+i.Overlay)
178+
}
179+
}
174180

175181
switch i.Verb {
176182
case "env", "version":
@@ -189,6 +195,7 @@ func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error {
189195
goArgs = append(goArgs, i.BuildFlags...)
190196
appendModFile()
191197
appendModFlag()
198+
appendOverlayFlag()
192199
goArgs = append(goArgs, i.Args...)
193200
}
194201
cmd := exec.Command("go", goArgs...)

0 commit comments

Comments
 (0)