1
0
mirror of https://github.com/golang/go synced 2024-11-17 03:14:50 -07:00

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 <matloob@golang.org>
Run-TryBot: Michael Matloob <matloob@golang.org>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Bryan C. Mills <bcmills@google.com>
Reviewed-by: Jay Conrod <jayconrod@google.com>
This commit is contained in:
Michael Matloob 2020-10-15 11:45:32 -04:00
parent c9c64886ef
commit 5e9582e3f0
4 changed files with 190 additions and 13 deletions

View File

@ -434,29 +434,39 @@ func Walk(root string, walkFn filepath.WalkFunc) error {
// lstat implements a version of os.Lstat that operates on the overlay filesystem.
func lstat(path string) (fs.FileInfo, error) {
return overlayStat(path, os.Lstat, "lstat")
}
// Stat implements a version of os.Stat that operates on the overlay filesystem.
func Stat(path string) (fs.FileInfo, error) {
return overlayStat(path, os.Stat, "stat")
}
// overlayStat implements lstat or Stat (depending on whether os.Lstat or os.Stat is passed in).
func overlayStat(path string, osStat func(string) (fs.FileInfo, error), opName string) (fs.FileInfo, error) {
cpath := canonicalize(path)
if _, ok := parentIsOverlayFile(filepath.Dir(cpath)); ok {
return nil, &fs.PathError{Op: "lstat", Path: cpath, Err: fs.ErrNotExist}
return nil, &fs.PathError{Op: opName, Path: cpath, Err: fs.ErrNotExist}
}
node, ok := overlay[cpath]
if !ok {
// The file or directory is not overlaid.
return os.Lstat(cpath)
return osStat(path)
}
switch {
case node.isDeleted():
return nil, &fs.PathError{Op: "lstat", Path: cpath, Err: fs.ErrNotExist}
case node.isDir():
return fakeDir(filepath.Base(cpath)), nil
return fakeDir(filepath.Base(path)), nil
default:
fi, err := os.Lstat(node.actualFilePath)
fi, err := osStat(node.actualFilePath)
if err != nil {
return nil, err
}
return fakeFile{name: filepath.Base(cpath), real: fi}, nil
return fakeFile{name: filepath.Base(path), real: fi}, nil
}
}

View File

@ -506,7 +506,7 @@ func TestWalk(t *testing.T) {
`,
".",
[]file{
{".", "root", 0, fs.ModeDir | 0700, true},
{".", ".", 0, fs.ModeDir | 0700, true},
{"file.txt", "file.txt", 0, 0600, false},
},
},
@ -522,7 +522,7 @@ contents of other file
`,
".",
[]file{
{".", "root", 0, fs.ModeDir | 0500, true},
{".", ".", 0, fs.ModeDir | 0500, true},
{"file.txt", "file.txt", 23, 0600, false},
{"other.txt", "other.txt", 23, 0600, false},
},
@ -538,7 +538,7 @@ contents of other file
`,
".",
[]file{
{".", "root", 0, fs.ModeDir | 0500, true},
{".", ".", 0, fs.ModeDir | 0500, true},
{"file.txt", "file.txt", 23, 0600, false},
{"other.txt", "other.txt", 23, 0600, false},
},
@ -554,7 +554,7 @@ contents of other file
`,
".",
[]file{
{".", "root", 0, fs.ModeDir | 0500, true},
{".", ".", 0, fs.ModeDir | 0500, true},
{"dir", "dir", 0, fs.ModeDir | 0500, true},
{"dir" + string(filepath.Separator) + "file.txt", "file.txt", 23, 0600, false},
{"other.txt", "other.txt", 23, 0600, false},
@ -818,3 +818,150 @@ contents`,
})
}
}
func TestStat(t *testing.T) {
testenv.MustHaveSymlink(t)
type file struct {
name string
size int64
mode os.FileMode // mode & (os.ModeDir|0x700): only check 'user' permissions
isDir bool
}
testCases := []struct {
name string
overlay string
path string
want file
wantErr bool
}{
{
"regular_file",
`{}
-- file.txt --
contents`,
"file.txt",
file{"file.txt", 9, 0600, false},
false,
},
{
"new_file_in_overlay",
`{"Replace": {"file.txt": "dummy.txt"}}
-- dummy.txt --
contents`,
"file.txt",
file{"file.txt", 9, 0600, false},
false,
},
{
"file_replaced_in_overlay",
`{"Replace": {"file.txt": "dummy.txt"}}
-- file.txt --
-- dummy.txt --
contents`,
"file.txt",
file{"file.txt", 9, 0600, false},
false,
},
{
"file_cant_exist",
`{"Replace": {"deleted": "dummy.txt"}}
-- deleted/file.txt --
-- dummy.txt --
`,
"deleted/file.txt",
file{},
true,
},
{
"deleted",
`{"Replace": {"deleted": ""}}
-- deleted --
`,
"deleted",
file{},
true,
},
{
"dir_on_disk",
`{}
-- dir/foo.txt --
`,
"dir",
file{"dir", 0, 0700 | os.ModeDir, true},
false,
},
{
"dir_in_overlay",
`{"Replace": {"dir/file.txt": "dummy.txt"}}
-- dummy.txt --
`,
"dir",
file{"dir", 0, 0500 | os.ModeDir, true},
false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
initOverlay(t, tc.overlay)
got, err := Stat(tc.path)
if tc.wantErr {
if err == nil {
t.Errorf("Stat(%q): got no error, want error", tc.path)
}
return
}
if err != nil {
t.Fatalf("Stat(%q): got error %v, want no error", tc.path, err)
}
if got.Name() != tc.want.name {
t.Errorf("Stat(%q).Name(): got %q, want %q", tc.path, got.Name(), tc.want.name)
}
if got.Mode()&(os.ModeDir|0700) != tc.want.mode {
t.Errorf("Stat(%q).Mode()&(os.ModeDir|0700): got %v, want %v", tc.path, got.Mode()&(os.ModeDir|0700), tc.want.mode)
}
if got.IsDir() != tc.want.isDir {
t.Errorf("Stat(%q).IsDir(): got %v, want %v", tc.path, got.IsDir(), tc.want.isDir)
}
if tc.want.isDir {
return // don't check size for directories
}
if got.Size() != tc.want.size {
t.Errorf("Stat(%q).Size(): got %v, want %v", tc.path, got.Size(), tc.want.size)
}
})
}
}
func TestStat_Symlink(t *testing.T) {
testenv.MustHaveSymlink(t)
initOverlay(t, `{
"Replace": {"file.go": "symlink"}
}
-- to.go --
0123456789
`)
// Create symlink
if err := os.Symlink("to.go", "symlink"); err != nil {
t.Error(err)
}
f := "file.go"
fi, err := Stat(f)
if err != nil {
t.Errorf("Stat(%q): got error %q, want nil error", f, err)
}
if !fi.Mode().IsRegular() {
t.Errorf("Stat(%q).Mode(): got %v, want regular mode", f, fi.Mode())
}
if fi.Size() != 11 {
t.Errorf("Stat(%q).Size(): got %v, want 11", f, fi.Size())
}
}

View File

@ -28,6 +28,7 @@ import (
"cmd/go/internal/base"
"cmd/go/internal/cfg"
"cmd/go/internal/fsys"
"cmd/go/internal/modinfo"
"cmd/go/internal/modload"
"cmd/go/internal/par"
@ -977,7 +978,7 @@ var isDirCache par.Cache
func isDir(path string) bool {
return isDirCache.Do(path, func() interface{} {
fi, err := os.Stat(path)
fi, err := fsys.Stat(path)
return err == nil && fi.IsDir()
}).(bool)
}
@ -2145,7 +2146,7 @@ func PackagesAndErrors(ctx context.Context, patterns []string) []*Package {
if strings.HasSuffix(p, ".go") {
// We need to test whether the path is an actual Go file and not a
// package path or pattern ending in '.go' (see golang.org/issue/34653).
if fi, err := os.Stat(p); err == nil && !fi.IsDir() {
if fi, err := fsys.Stat(p); err == nil && !fi.IsDir() {
return []*Package{GoFilesPackage(ctx, patterns)}
}
}
@ -2305,7 +2306,7 @@ func GoFilesPackage(ctx context.Context, gofiles []string) *Package {
var dirent []fs.FileInfo
var dir string
for _, file := range gofiles {
fi, err := os.Stat(file)
fi, err := fsys.Stat(file)
if err != nil {
base.Fatalf("%s", err)
}

View File

@ -24,6 +24,11 @@ go build -overlay overlay.json -o print_trimpath$GOEXE -trimpath ./printpath
exec ./print_trimpath$GOEXE
stdout ^m[/\\]printpath[/\\]main.go
go build -overlay overlay.json -o print_trimpath_two_files$GOEXE printpath/main.go printpath/other.go
exec ./print_trimpath_two_files$GOEXE
stdout $WORK[/\\]gopath[/\\]src[/\\]m[/\\]printpath[/\\]main.go
stdout $WORK[/\\]gopath[/\\]src[/\\]m[/\\]printpath[/\\]other.go
# Run same tests but with gccgo.
env GO111MODULE=off
[!exec:gccgo] stop
@ -65,7 +70,8 @@ the actual code is in the overlay
"f.go": "overlay/f.go",
"dir/g.go": "overlay/dir_g.go",
"dir2/i.go": "overlay/dir2_i.go",
"printpath/main.go": "overlay/printpath.go"
"printpath/main.go": "overlay/printpath.go",
"printpath/other.go": "overlay2/printpath2.go"
}
}
-- m/overlay/f.go --
@ -101,6 +107,19 @@ func main() {
// paths.
fmt.Println(filepath.FromSlash(file))
}
-- m/overlay2/printpath2.go --
package main
import (
"fmt"
"path/filepath"
"runtime"
)
func init() {
_, file, _, _ := runtime.Caller(0)
fmt.Println(filepath.FromSlash(file))
}
-- m/overlay/dir2_i.go --
package dir2