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:
parent
c9c64886ef
commit
5e9582e3f0
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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())
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
21
src/cmd/go/testdata/script/build_overlay.txt
vendored
21
src/cmd/go/testdata/script/build_overlay.txt
vendored
@ -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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user