Skip to content

Commit 5e9582e

Browse files
committed
cmd/go: support overlays for synthesized packages.
The main missing piece here was supporting Stat in the overlay filesystem, in the parts of the package code that determines whether an command line argument is a file on disk or a directory. so this change adds a Stat function to the fsys package. It's implemented the same way as the already existing fsys.lstat function, but instead of os.Lstat, it calls os.Stat on disk files. Then, the change changes parts of the package code to use the overlay Stat instead of the os package's Stat. For #39958 Change-Id: I8e478ae386f05b48d7dd71bd7e47584f090623df Reviewed-on: https://go-review.googlesource.com/c/go/+/262617 Trust: Michael Matloob <[email protected]> Run-TryBot: Michael Matloob <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Bryan C. Mills <[email protected]> Reviewed-by: Jay Conrod <[email protected]>
1 parent c9c6488 commit 5e9582e

File tree

4 files changed

+190
-13
lines changed

4 files changed

+190
-13
lines changed

src/cmd/go/internal/fsys/fsys.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -434,29 +434,39 @@ func Walk(root string, walkFn filepath.WalkFunc) error {
434434

435435
// lstat implements a version of os.Lstat that operates on the overlay filesystem.
436436
func lstat(path string) (fs.FileInfo, error) {
437+
return overlayStat(path, os.Lstat, "lstat")
438+
}
439+
440+
// Stat implements a version of os.Stat that operates on the overlay filesystem.
441+
func Stat(path string) (fs.FileInfo, error) {
442+
return overlayStat(path, os.Stat, "stat")
443+
}
444+
445+
// overlayStat implements lstat or Stat (depending on whether os.Lstat or os.Stat is passed in).
446+
func overlayStat(path string, osStat func(string) (fs.FileInfo, error), opName string) (fs.FileInfo, error) {
437447
cpath := canonicalize(path)
438448

439449
if _, ok := parentIsOverlayFile(filepath.Dir(cpath)); ok {
440-
return nil, &fs.PathError{Op: "lstat", Path: cpath, Err: fs.ErrNotExist}
450+
return nil, &fs.PathError{Op: opName, Path: cpath, Err: fs.ErrNotExist}
441451
}
442452

443453
node, ok := overlay[cpath]
444454
if !ok {
445455
// The file or directory is not overlaid.
446-
return os.Lstat(cpath)
456+
return osStat(path)
447457
}
448458

449459
switch {
450460
case node.isDeleted():
451461
return nil, &fs.PathError{Op: "lstat", Path: cpath, Err: fs.ErrNotExist}
452462
case node.isDir():
453-
return fakeDir(filepath.Base(cpath)), nil
463+
return fakeDir(filepath.Base(path)), nil
454464
default:
455-
fi, err := os.Lstat(node.actualFilePath)
465+
fi, err := osStat(node.actualFilePath)
456466
if err != nil {
457467
return nil, err
458468
}
459-
return fakeFile{name: filepath.Base(cpath), real: fi}, nil
469+
return fakeFile{name: filepath.Base(path), real: fi}, nil
460470
}
461471
}
462472

src/cmd/go/internal/fsys/fsys_test.go

Lines changed: 151 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -506,7 +506,7 @@ func TestWalk(t *testing.T) {
506506
`,
507507
".",
508508
[]file{
509-
{".", "root", 0, fs.ModeDir | 0700, true},
509+
{".", ".", 0, fs.ModeDir | 0700, true},
510510
{"file.txt", "file.txt", 0, 0600, false},
511511
},
512512
},
@@ -522,7 +522,7 @@ contents of other file
522522
`,
523523
".",
524524
[]file{
525-
{".", "root", 0, fs.ModeDir | 0500, true},
525+
{".", ".", 0, fs.ModeDir | 0500, true},
526526
{"file.txt", "file.txt", 23, 0600, false},
527527
{"other.txt", "other.txt", 23, 0600, false},
528528
},
@@ -538,7 +538,7 @@ contents of other file
538538
`,
539539
".",
540540
[]file{
541-
{".", "root", 0, fs.ModeDir | 0500, true},
541+
{".", ".", 0, fs.ModeDir | 0500, true},
542542
{"file.txt", "file.txt", 23, 0600, false},
543543
{"other.txt", "other.txt", 23, 0600, false},
544544
},
@@ -554,7 +554,7 @@ contents of other file
554554
`,
555555
".",
556556
[]file{
557-
{".", "root", 0, fs.ModeDir | 0500, true},
557+
{".", ".", 0, fs.ModeDir | 0500, true},
558558
{"dir", "dir", 0, fs.ModeDir | 0500, true},
559559
{"dir" + string(filepath.Separator) + "file.txt", "file.txt", 23, 0600, false},
560560
{"other.txt", "other.txt", 23, 0600, false},
@@ -818,3 +818,150 @@ contents`,
818818
})
819819
}
820820
}
821+
822+
func TestStat(t *testing.T) {
823+
testenv.MustHaveSymlink(t)
824+
825+
type file struct {
826+
name string
827+
size int64
828+
mode os.FileMode // mode & (os.ModeDir|0x700): only check 'user' permissions
829+
isDir bool
830+
}
831+
832+
testCases := []struct {
833+
name string
834+
overlay string
835+
path string
836+
837+
want file
838+
wantErr bool
839+
}{
840+
{
841+
"regular_file",
842+
`{}
843+
-- file.txt --
844+
contents`,
845+
"file.txt",
846+
file{"file.txt", 9, 0600, false},
847+
false,
848+
},
849+
{
850+
"new_file_in_overlay",
851+
`{"Replace": {"file.txt": "dummy.txt"}}
852+
-- dummy.txt --
853+
contents`,
854+
"file.txt",
855+
file{"file.txt", 9, 0600, false},
856+
false,
857+
},
858+
{
859+
"file_replaced_in_overlay",
860+
`{"Replace": {"file.txt": "dummy.txt"}}
861+
-- file.txt --
862+
-- dummy.txt --
863+
contents`,
864+
"file.txt",
865+
file{"file.txt", 9, 0600, false},
866+
false,
867+
},
868+
{
869+
"file_cant_exist",
870+
`{"Replace": {"deleted": "dummy.txt"}}
871+
-- deleted/file.txt --
872+
-- dummy.txt --
873+
`,
874+
"deleted/file.txt",
875+
file{},
876+
true,
877+
},
878+
{
879+
"deleted",
880+
`{"Replace": {"deleted": ""}}
881+
-- deleted --
882+
`,
883+
"deleted",
884+
file{},
885+
true,
886+
},
887+
{
888+
"dir_on_disk",
889+
`{}
890+
-- dir/foo.txt --
891+
`,
892+
"dir",
893+
file{"dir", 0, 0700 | os.ModeDir, true},
894+
false,
895+
},
896+
{
897+
"dir_in_overlay",
898+
`{"Replace": {"dir/file.txt": "dummy.txt"}}
899+
-- dummy.txt --
900+
`,
901+
"dir",
902+
file{"dir", 0, 0500 | os.ModeDir, true},
903+
false,
904+
},
905+
}
906+
907+
for _, tc := range testCases {
908+
t.Run(tc.name, func(t *testing.T) {
909+
initOverlay(t, tc.overlay)
910+
got, err := Stat(tc.path)
911+
if tc.wantErr {
912+
if err == nil {
913+
t.Errorf("Stat(%q): got no error, want error", tc.path)
914+
}
915+
return
916+
}
917+
if err != nil {
918+
t.Fatalf("Stat(%q): got error %v, want no error", tc.path, err)
919+
}
920+
if got.Name() != tc.want.name {
921+
t.Errorf("Stat(%q).Name(): got %q, want %q", tc.path, got.Name(), tc.want.name)
922+
}
923+
if got.Mode()&(os.ModeDir|0700) != tc.want.mode {
924+
t.Errorf("Stat(%q).Mode()&(os.ModeDir|0700): got %v, want %v", tc.path, got.Mode()&(os.ModeDir|0700), tc.want.mode)
925+
}
926+
if got.IsDir() != tc.want.isDir {
927+
t.Errorf("Stat(%q).IsDir(): got %v, want %v", tc.path, got.IsDir(), tc.want.isDir)
928+
}
929+
if tc.want.isDir {
930+
return // don't check size for directories
931+
}
932+
if got.Size() != tc.want.size {
933+
t.Errorf("Stat(%q).Size(): got %v, want %v", tc.path, got.Size(), tc.want.size)
934+
}
935+
})
936+
}
937+
}
938+
939+
func TestStat_Symlink(t *testing.T) {
940+
testenv.MustHaveSymlink(t)
941+
942+
initOverlay(t, `{
943+
"Replace": {"file.go": "symlink"}
944+
}
945+
-- to.go --
946+
0123456789
947+
`)
948+
949+
// Create symlink
950+
if err := os.Symlink("to.go", "symlink"); err != nil {
951+
t.Error(err)
952+
}
953+
954+
f := "file.go"
955+
fi, err := Stat(f)
956+
if err != nil {
957+
t.Errorf("Stat(%q): got error %q, want nil error", f, err)
958+
}
959+
960+
if !fi.Mode().IsRegular() {
961+
t.Errorf("Stat(%q).Mode(): got %v, want regular mode", f, fi.Mode())
962+
}
963+
964+
if fi.Size() != 11 {
965+
t.Errorf("Stat(%q).Size(): got %v, want 11", f, fi.Size())
966+
}
967+
}

src/cmd/go/internal/load/pkg.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import (
2828

2929
"cmd/go/internal/base"
3030
"cmd/go/internal/cfg"
31+
"cmd/go/internal/fsys"
3132
"cmd/go/internal/modinfo"
3233
"cmd/go/internal/modload"
3334
"cmd/go/internal/par"
@@ -977,7 +978,7 @@ var isDirCache par.Cache
977978

978979
func isDir(path string) bool {
979980
return isDirCache.Do(path, func() interface{} {
980-
fi, err := os.Stat(path)
981+
fi, err := fsys.Stat(path)
981982
return err == nil && fi.IsDir()
982983
}).(bool)
983984
}
@@ -2145,7 +2146,7 @@ func PackagesAndErrors(ctx context.Context, patterns []string) []*Package {
21452146
if strings.HasSuffix(p, ".go") {
21462147
// We need to test whether the path is an actual Go file and not a
21472148
// package path or pattern ending in '.go' (see golang.org/issue/34653).
2148-
if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
2149+
if fi, err := fsys.Stat(p); err == nil && !fi.IsDir() {
21492150
return []*Package{GoFilesPackage(ctx, patterns)}
21502151
}
21512152
}
@@ -2305,7 +2306,7 @@ func GoFilesPackage(ctx context.Context, gofiles []string) *Package {
23052306
var dirent []fs.FileInfo
23062307
var dir string
23072308
for _, file := range gofiles {
2308-
fi, err := os.Stat(file)
2309+
fi, err := fsys.Stat(file)
23092310
if err != nil {
23102311
base.Fatalf("%s", err)
23112312
}

src/cmd/go/testdata/script/build_overlay.txt

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,11 @@ go build -overlay overlay.json -o print_trimpath$GOEXE -trimpath ./printpath
2424
exec ./print_trimpath$GOEXE
2525
stdout ^m[/\\]printpath[/\\]main.go
2626

27+
go build -overlay overlay.json -o print_trimpath_two_files$GOEXE printpath/main.go printpath/other.go
28+
exec ./print_trimpath_two_files$GOEXE
29+
stdout $WORK[/\\]gopath[/\\]src[/\\]m[/\\]printpath[/\\]main.go
30+
stdout $WORK[/\\]gopath[/\\]src[/\\]m[/\\]printpath[/\\]other.go
31+
2732
# Run same tests but with gccgo.
2833
env GO111MODULE=off
2934
[!exec:gccgo] stop
@@ -65,7 +70,8 @@ the actual code is in the overlay
6570
"f.go": "overlay/f.go",
6671
"dir/g.go": "overlay/dir_g.go",
6772
"dir2/i.go": "overlay/dir2_i.go",
68-
"printpath/main.go": "overlay/printpath.go"
73+
"printpath/main.go": "overlay/printpath.go",
74+
"printpath/other.go": "overlay2/printpath2.go"
6975
}
7076
}
7177
-- m/overlay/f.go --
@@ -101,6 +107,19 @@ func main() {
101107
// paths.
102108
fmt.Println(filepath.FromSlash(file))
103109
}
110+
-- m/overlay2/printpath2.go --
111+
package main
112+
113+
import (
114+
"fmt"
115+
"path/filepath"
116+
"runtime"
117+
)
118+
119+
func init() {
120+
_, file, _, _ := runtime.Caller(0)
121+
fmt.Println(filepath.FromSlash(file))
122+
}
104123
-- m/overlay/dir2_i.go --
105124
package dir2
106125

0 commit comments

Comments
 (0)