67c99255cc
Replace the individual matches and deps return values with a GlobResult struct. Use the GlobResult to create the file list file in GlobWithDepFile (used by bpglob) and in the glob singleton, as the generated files must match exactly so that soong_build is not rerun. Bug: 159845846 Test: glob_test.go Change-Id: I2159cc9d85f388073198eac7456e5bf43e813096
577 lines
13 KiB
Go
577 lines
13 KiB
Go
// Copyright 2016 Google Inc. All rights reserved.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package pathtools
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// Based on Andrew Gerrand's "10 things you (probably) dont' know about Go"
|
|
|
|
type ShouldFollowSymlinks bool
|
|
|
|
const (
|
|
FollowSymlinks = ShouldFollowSymlinks(true)
|
|
DontFollowSymlinks = ShouldFollowSymlinks(false)
|
|
)
|
|
|
|
var OsFs FileSystem = &osFs{}
|
|
|
|
func MockFs(files map[string][]byte) FileSystem {
|
|
fs := &mockFs{
|
|
files: make(map[string][]byte, len(files)),
|
|
dirs: make(map[string]bool),
|
|
symlinks: make(map[string]string),
|
|
all: []string(nil),
|
|
}
|
|
|
|
for f, b := range files {
|
|
if tokens := strings.SplitN(f, "->", 2); len(tokens) == 2 {
|
|
fs.symlinks[strings.TrimSpace(tokens[0])] = strings.TrimSpace(tokens[1])
|
|
continue
|
|
}
|
|
|
|
fs.files[filepath.Clean(f)] = b
|
|
dir := filepath.Dir(f)
|
|
for dir != "." && dir != "/" {
|
|
fs.dirs[dir] = true
|
|
dir = filepath.Dir(dir)
|
|
}
|
|
fs.dirs[dir] = true
|
|
}
|
|
|
|
fs.dirs["."] = true
|
|
fs.dirs["/"] = true
|
|
|
|
for f := range fs.files {
|
|
fs.all = append(fs.all, f)
|
|
}
|
|
|
|
for d := range fs.dirs {
|
|
fs.all = append(fs.all, d)
|
|
}
|
|
|
|
for s := range fs.symlinks {
|
|
fs.all = append(fs.all, s)
|
|
}
|
|
|
|
sort.Strings(fs.all)
|
|
|
|
return fs
|
|
}
|
|
|
|
type ReaderAtSeekerCloser interface {
|
|
io.Reader
|
|
io.ReaderAt
|
|
io.Seeker
|
|
io.Closer
|
|
}
|
|
|
|
type FileSystem interface {
|
|
// Open opens a file for reading. Follows symlinks.
|
|
Open(name string) (ReaderAtSeekerCloser, error)
|
|
|
|
// Exists returns whether the file exists and whether it is a directory. Follows symlinks.
|
|
Exists(name string) (bool, bool, error)
|
|
|
|
Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error)
|
|
glob(pattern string) (matches []string, err error)
|
|
|
|
// IsDir returns true if the path points to a directory, false it it points to a file. Follows symlinks.
|
|
// Returns os.ErrNotExist if the path does not exist or is a symlink to a path that does not exist.
|
|
IsDir(name string) (bool, error)
|
|
|
|
// IsSymlink returns true if the path points to a symlink, even if that symlink points to a path that does
|
|
// not exist. Returns os.ErrNotExist if the path does not exist.
|
|
IsSymlink(name string) (bool, error)
|
|
|
|
// Lstat returns info on a file without following symlinks.
|
|
Lstat(name string) (os.FileInfo, error)
|
|
|
|
// Lstat returns info on a file.
|
|
Stat(name string) (os.FileInfo, error)
|
|
|
|
// ListDirsRecursive returns a list of all the directories in a path, following symlinks if requested.
|
|
ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error)
|
|
|
|
// ReadDirNames returns a list of everything in a directory.
|
|
ReadDirNames(name string) ([]string, error)
|
|
|
|
// Readlink returns the destination of the named symbolic link.
|
|
Readlink(name string) (string, error)
|
|
}
|
|
|
|
// osFs implements FileSystem using the local disk.
|
|
type osFs struct {
|
|
srcDir string
|
|
}
|
|
|
|
func NewOsFs(path string) FileSystem {
|
|
return &osFs{srcDir: path}
|
|
}
|
|
|
|
func (fs *osFs) toAbs(path string) string {
|
|
if filepath.IsAbs(path) {
|
|
return path
|
|
}
|
|
return filepath.Join(fs.srcDir, path)
|
|
}
|
|
|
|
func (fs *osFs) removeSrcDirPrefix(path string) string {
|
|
if fs.srcDir == "" {
|
|
return path
|
|
}
|
|
rel, err := filepath.Rel(fs.srcDir, path)
|
|
if err != nil {
|
|
panic(fmt.Errorf("unexpected failure in removeSrcDirPrefix filepath.Rel(%s, %s): %s",
|
|
fs.srcDir, path, err))
|
|
}
|
|
if strings.HasPrefix(rel, "../") {
|
|
panic(fmt.Errorf("unexpected relative path outside directory in removeSrcDirPrefix filepath.Rel(%s, %s): %s",
|
|
fs.srcDir, path, rel))
|
|
}
|
|
return rel
|
|
}
|
|
|
|
func (fs *osFs) removeSrcDirPrefixes(paths []string) []string {
|
|
if fs.srcDir != "" {
|
|
for i, path := range paths {
|
|
paths[i] = fs.removeSrcDirPrefix(path)
|
|
}
|
|
}
|
|
return paths
|
|
}
|
|
|
|
func (fs *osFs) Open(name string) (ReaderAtSeekerCloser, error) {
|
|
return os.Open(fs.toAbs(name))
|
|
}
|
|
|
|
func (fs *osFs) Exists(name string) (bool, bool, error) {
|
|
stat, err := os.Stat(fs.toAbs(name))
|
|
if err == nil {
|
|
return true, stat.IsDir(), nil
|
|
} else if os.IsNotExist(err) {
|
|
return false, false, nil
|
|
} else {
|
|
return false, false, err
|
|
}
|
|
}
|
|
|
|
func (fs *osFs) IsDir(name string) (bool, error) {
|
|
info, err := os.Stat(fs.toAbs(name))
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return info.IsDir(), nil
|
|
}
|
|
|
|
func (fs *osFs) IsSymlink(name string) (bool, error) {
|
|
if info, err := os.Lstat(fs.toAbs(name)); err != nil {
|
|
return false, err
|
|
} else {
|
|
return info.Mode()&os.ModeSymlink != 0, nil
|
|
}
|
|
}
|
|
|
|
func (fs *osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) {
|
|
return startGlob(fs, pattern, excludes, follow)
|
|
}
|
|
|
|
func (fs *osFs) glob(pattern string) ([]string, error) {
|
|
paths, err := filepath.Glob(fs.toAbs(pattern))
|
|
fs.removeSrcDirPrefixes(paths)
|
|
return paths, err
|
|
}
|
|
|
|
func (fs *osFs) Lstat(path string) (stats os.FileInfo, err error) {
|
|
return os.Lstat(fs.toAbs(path))
|
|
}
|
|
|
|
func (fs *osFs) Stat(path string) (stats os.FileInfo, err error) {
|
|
return os.Stat(fs.toAbs(path))
|
|
}
|
|
|
|
// Returns a list of all directories under dir
|
|
func (fs *osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) {
|
|
return listDirsRecursive(fs, name, follow)
|
|
}
|
|
|
|
func (fs *osFs) ReadDirNames(name string) ([]string, error) {
|
|
dir, err := os.Open(fs.toAbs(name))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer dir.Close()
|
|
|
|
contents, err := dir.Readdirnames(-1)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
sort.Strings(contents)
|
|
return contents, nil
|
|
}
|
|
|
|
func (fs *osFs) Readlink(name string) (string, error) {
|
|
return os.Readlink(fs.toAbs(name))
|
|
}
|
|
|
|
type mockFs struct {
|
|
files map[string][]byte
|
|
dirs map[string]bool
|
|
symlinks map[string]string
|
|
all []string
|
|
}
|
|
|
|
func (m *mockFs) followSymlinks(name string) string {
|
|
dir, file := saneSplit(name)
|
|
if dir != "." && dir != "/" {
|
|
dir = m.followSymlinks(dir)
|
|
}
|
|
name = filepath.Join(dir, file)
|
|
|
|
for i := 0; i < 255; i++ {
|
|
i++
|
|
if i > 255 {
|
|
panic("symlink loop")
|
|
}
|
|
to, exists := m.symlinks[name]
|
|
if !exists {
|
|
break
|
|
}
|
|
if filepath.IsAbs(to) {
|
|
name = to
|
|
} else {
|
|
name = filepath.Join(dir, to)
|
|
}
|
|
}
|
|
return name
|
|
}
|
|
|
|
func (m *mockFs) Open(name string) (ReaderAtSeekerCloser, error) {
|
|
name = filepath.Clean(name)
|
|
name = m.followSymlinks(name)
|
|
if f, ok := m.files[name]; ok {
|
|
return struct {
|
|
io.Closer
|
|
*bytes.Reader
|
|
}{
|
|
ioutil.NopCloser(nil),
|
|
bytes.NewReader(f),
|
|
}, nil
|
|
}
|
|
|
|
return nil, &os.PathError{
|
|
Op: "open",
|
|
Path: name,
|
|
Err: os.ErrNotExist,
|
|
}
|
|
}
|
|
|
|
func (m *mockFs) Exists(name string) (bool, bool, error) {
|
|
name = filepath.Clean(name)
|
|
name = m.followSymlinks(name)
|
|
if _, ok := m.files[name]; ok {
|
|
return ok, false, nil
|
|
}
|
|
if _, ok := m.dirs[name]; ok {
|
|
return ok, true, nil
|
|
}
|
|
return false, false, nil
|
|
}
|
|
|
|
func (m *mockFs) IsDir(name string) (bool, error) {
|
|
dir := filepath.Dir(name)
|
|
if dir != "." && dir != "/" {
|
|
isDir, err := m.IsDir(dir)
|
|
|
|
if serr, ok := err.(*os.SyscallError); ok && serr.Err == syscall.ENOTDIR {
|
|
isDir = false
|
|
} else if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if !isDir {
|
|
return false, os.NewSyscallError("stat "+name, syscall.ENOTDIR)
|
|
}
|
|
}
|
|
|
|
name = filepath.Clean(name)
|
|
name = m.followSymlinks(name)
|
|
|
|
if _, ok := m.dirs[name]; ok {
|
|
return true, nil
|
|
}
|
|
if _, ok := m.files[name]; ok {
|
|
return false, nil
|
|
}
|
|
return false, os.ErrNotExist
|
|
}
|
|
|
|
func (m *mockFs) IsSymlink(name string) (bool, error) {
|
|
dir, file := saneSplit(name)
|
|
dir = m.followSymlinks(dir)
|
|
name = filepath.Join(dir, file)
|
|
|
|
if _, isSymlink := m.symlinks[name]; isSymlink {
|
|
return true, nil
|
|
}
|
|
if _, isDir := m.dirs[name]; isDir {
|
|
return false, nil
|
|
}
|
|
if _, isFile := m.files[name]; isFile {
|
|
return false, nil
|
|
}
|
|
return false, os.ErrNotExist
|
|
}
|
|
|
|
func (m *mockFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (GlobResult, error) {
|
|
return startGlob(m, pattern, excludes, follow)
|
|
}
|
|
|
|
func unescapeGlob(s string) string {
|
|
i := 0
|
|
for i < len(s) {
|
|
if s[i] == '\\' {
|
|
s = s[:i] + s[i+1:]
|
|
} else {
|
|
i++
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
func (m *mockFs) glob(pattern string) ([]string, error) {
|
|
dir, file := saneSplit(pattern)
|
|
|
|
dir = unescapeGlob(dir)
|
|
toDir := m.followSymlinks(dir)
|
|
|
|
var matches []string
|
|
for _, f := range m.all {
|
|
fDir, fFile := saneSplit(f)
|
|
if toDir == fDir {
|
|
match, err := filepath.Match(file, fFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if (f == "." || f == "/") && f != pattern {
|
|
// filepath.Glob won't return "." or "/" unless the pattern was "." or "/"
|
|
match = false
|
|
}
|
|
if match {
|
|
matches = append(matches, filepath.Join(dir, fFile))
|
|
}
|
|
}
|
|
}
|
|
return matches, nil
|
|
}
|
|
|
|
type mockStat struct {
|
|
name string
|
|
size int64
|
|
mode os.FileMode
|
|
}
|
|
|
|
func (ms *mockStat) Name() string { return ms.name }
|
|
func (ms *mockStat) IsDir() bool { return ms.Mode().IsDir() }
|
|
func (ms *mockStat) Size() int64 { return ms.size }
|
|
func (ms *mockStat) Mode() os.FileMode { return ms.mode }
|
|
func (ms *mockStat) ModTime() time.Time { return time.Time{} }
|
|
func (ms *mockStat) Sys() interface{} { return nil }
|
|
|
|
func (m *mockFs) Lstat(name string) (os.FileInfo, error) {
|
|
dir, file := saneSplit(name)
|
|
dir = m.followSymlinks(dir)
|
|
name = filepath.Join(dir, file)
|
|
|
|
ms := mockStat{
|
|
name: file,
|
|
}
|
|
|
|
if symlink, isSymlink := m.symlinks[name]; isSymlink {
|
|
ms.mode = os.ModeSymlink
|
|
ms.size = int64(len(symlink))
|
|
} else if _, isDir := m.dirs[name]; isDir {
|
|
ms.mode = os.ModeDir
|
|
} else if _, isFile := m.files[name]; isFile {
|
|
ms.mode = 0
|
|
ms.size = int64(len(m.files[name]))
|
|
} else {
|
|
return nil, os.ErrNotExist
|
|
}
|
|
|
|
return &ms, nil
|
|
}
|
|
|
|
func (m *mockFs) Stat(name string) (os.FileInfo, error) {
|
|
name = filepath.Clean(name)
|
|
origName := name
|
|
name = m.followSymlinks(name)
|
|
|
|
ms := mockStat{
|
|
name: filepath.Base(origName),
|
|
size: int64(len(m.files[name])),
|
|
}
|
|
|
|
if _, isDir := m.dirs[name]; isDir {
|
|
ms.mode = os.ModeDir
|
|
} else if _, isFile := m.files[name]; isFile {
|
|
ms.mode = 0
|
|
ms.size = int64(len(m.files[name]))
|
|
} else {
|
|
return nil, os.ErrNotExist
|
|
}
|
|
|
|
return &ms, nil
|
|
}
|
|
|
|
func (m *mockFs) ReadDirNames(name string) ([]string, error) {
|
|
name = filepath.Clean(name)
|
|
name = m.followSymlinks(name)
|
|
|
|
exists, isDir, err := m.Exists(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !exists {
|
|
return nil, os.ErrNotExist
|
|
}
|
|
if !isDir {
|
|
return nil, os.NewSyscallError("readdir", syscall.ENOTDIR)
|
|
}
|
|
|
|
var ret []string
|
|
for _, f := range m.all {
|
|
dir, file := saneSplit(f)
|
|
if dir == name && len(file) > 0 && file[0] != '.' {
|
|
ret = append(ret, file)
|
|
}
|
|
}
|
|
return ret, nil
|
|
}
|
|
|
|
func (m *mockFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) ([]string, error) {
|
|
return listDirsRecursive(m, name, follow)
|
|
}
|
|
|
|
func (m *mockFs) Readlink(name string) (string, error) {
|
|
dir, file := saneSplit(name)
|
|
dir = m.followSymlinks(dir)
|
|
|
|
origName := name
|
|
name = filepath.Join(dir, file)
|
|
|
|
if dest, isSymlink := m.symlinks[name]; isSymlink {
|
|
return dest, nil
|
|
}
|
|
|
|
if exists, _, err := m.Exists(name); err != nil {
|
|
return "", err
|
|
} else if !exists {
|
|
return "", os.ErrNotExist
|
|
} else {
|
|
return "", os.NewSyscallError("readlink: "+origName, syscall.EINVAL)
|
|
}
|
|
}
|
|
|
|
func listDirsRecursive(fs FileSystem, name string, follow ShouldFollowSymlinks) ([]string, error) {
|
|
name = filepath.Clean(name)
|
|
|
|
isDir, err := fs.IsDir(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !isDir {
|
|
return nil, nil
|
|
}
|
|
|
|
dirs := []string{name}
|
|
|
|
subDirs, err := listDirsRecursiveRelative(fs, name, follow, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, d := range subDirs {
|
|
dirs = append(dirs, filepath.Join(name, d))
|
|
}
|
|
|
|
return dirs, nil
|
|
}
|
|
|
|
func listDirsRecursiveRelative(fs FileSystem, name string, follow ShouldFollowSymlinks, depth int) ([]string, error) {
|
|
depth++
|
|
if depth > 255 {
|
|
return nil, fmt.Errorf("too many symlinks")
|
|
}
|
|
contents, err := fs.ReadDirNames(name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var dirs []string
|
|
for _, f := range contents {
|
|
if f[0] == '.' {
|
|
continue
|
|
}
|
|
f = filepath.Join(name, f)
|
|
var info os.FileInfo
|
|
if follow == DontFollowSymlinks {
|
|
info, err = fs.Lstat(f)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if info.Mode()&os.ModeSymlink != 0 {
|
|
continue
|
|
}
|
|
} else {
|
|
info, err = fs.Stat(f)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
}
|
|
if info.IsDir() {
|
|
dirs = append(dirs, f)
|
|
subDirs, err := listDirsRecursiveRelative(fs, f, follow, depth)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, s := range subDirs {
|
|
dirs = append(dirs, filepath.Join(f, s))
|
|
}
|
|
}
|
|
}
|
|
|
|
for i, d := range dirs {
|
|
rel, err := filepath.Rel(name, d)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
dirs[i] = rel
|
|
}
|
|
|
|
return dirs, nil
|
|
}
|