Allow primary builder to change working directory

Bug: 146437378
Test: pathtools/fs_test.go
Change-Id: I513ceb9b8b0b4f18223bc34ecad9846fe220709b
This commit is contained in:
Colin Cross 2019-12-17 13:12:35 -08:00
parent 55ca1c0860
commit c5fa50e057
7 changed files with 311 additions and 173 deletions

View file

@ -691,7 +691,7 @@ func (s *singleton) GenerateBuildActions(ctx blueprint.SingletonContext) {
if s.config.stage == StagePrimary { if s.config.stage == StagePrimary {
mainNinjaFile := filepath.Join("$buildDir", "build.ninja") mainNinjaFile := filepath.Join("$buildDir", "build.ninja")
primaryBuilderNinjaGlobFile := filepath.Join(BuildDir, bootstrapSubDir, "build-globs.ninja") primaryBuilderNinjaGlobFile := absolutePath(filepath.Join(BuildDir, bootstrapSubDir, "build-globs.ninja"))
if _, err := os.Stat(primaryBuilderNinjaGlobFile); os.IsNotExist(err) { if _, err := os.Stat(primaryBuilderNinjaGlobFile); os.IsNotExist(err) {
err = ioutil.WriteFile(primaryBuilderNinjaGlobFile, nil, 0666) err = ioutil.WriteFile(primaryBuilderNinjaGlobFile, nil, 0666)

View file

@ -70,7 +70,7 @@ func removeAbandonedFilesUnder(ctx *blueprint.Context, config *Config,
for _, filePath := range filePaths { for _, filePath := range filePaths {
isTarget := targets[filePath] isTarget := targets[filePath]
if !isTarget { if !isTarget {
err = removeFileAndEmptyDirs(filePath) err = removeFileAndEmptyDirs(absolutePath(filePath))
if err != nil { if err != nil {
return err return err
} }

View file

@ -47,6 +47,8 @@ var (
BuildDir string BuildDir string
NinjaBuildDir string NinjaBuildDir string
SrcDir string SrcDir string
absSrcDir string
) )
func init() { func init() {
@ -76,8 +78,10 @@ func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...stri
debug.SetGCPercent(-1) debug.SetGCPercent(-1)
} }
absSrcDir = ctx.SrcDir()
if cpuprofile != "" { if cpuprofile != "" {
f, err := os.Create(cpuprofile) f, err := os.Create(absolutePath(cpuprofile))
if err != nil { if err != nil {
fatalf("error opening cpuprofile: %s", err) fatalf("error opening cpuprofile: %s", err)
} }
@ -87,7 +91,7 @@ func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...stri
} }
if traceFile != "" { if traceFile != "" {
f, err := os.Create(traceFile) f, err := os.Create(absolutePath(traceFile))
if err != nil { if err != nil {
fatalf("error opening trace: %s", err) fatalf("error opening trace: %s", err)
} }
@ -155,7 +159,7 @@ func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...stri
deps = append(deps, extraDeps...) deps = append(deps, extraDeps...)
if docFile != "" { if docFile != "" {
err := writeDocs(ctx, docFile) err := writeDocs(ctx, absolutePath(docFile))
if err != nil { if err != nil {
fatalErrors([]error{err}) fatalErrors([]error{err})
} }
@ -180,7 +184,7 @@ func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...stri
var buf *bufio.Writer var buf *bufio.Writer
if stage != StageMain || !emptyNinjaFile { if stage != StageMain || !emptyNinjaFile {
f, err = os.OpenFile(outFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, outFilePermissions) f, err = os.OpenFile(absolutePath(outFile), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, outFilePermissions)
if err != nil { if err != nil {
fatalf("error opening Ninja file: %s", err) fatalf("error opening Ninja file: %s", err)
} }
@ -215,14 +219,14 @@ func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...stri
fatalErrors(errs) fatalErrors(errs)
} }
err = ioutil.WriteFile(globFile, buffer, outFilePermissions) err = ioutil.WriteFile(absolutePath(globFile), buffer, outFilePermissions)
if err != nil { if err != nil {
fatalf("error writing %s: %s", outFile, err) fatalf("error writing %s: %s", globFile, err)
} }
} }
if depFile != "" { if depFile != "" {
err := deptools.WriteDepFile(depFile, outFile, deps) err := deptools.WriteDepFile(absolutePath(depFile), outFile, deps)
if err != nil { if err != nil {
fatalf("error writing depfile: %s", err) fatalf("error writing depfile: %s", err)
} }
@ -237,7 +241,7 @@ func Main(ctx *blueprint.Context, config interface{}, extraNinjaFileDeps ...stri
} }
if memprofile != "" { if memprofile != "" {
f, err := os.Create(memprofile) f, err := os.Create(absolutePath(memprofile))
if err != nil { if err != nil {
fatalf("error opening memprofile: %s", err) fatalf("error opening memprofile: %s", err)
} }
@ -268,3 +272,10 @@ func fatalErrors(errs []error) {
} }
os.Exit(1) os.Exit(1)
} }
func absolutePath(path string) string {
if filepath.IsAbs(path) {
return path
}
return filepath.Join(absSrcDir, path)
}

View file

@ -30,7 +30,7 @@ func bootstrapVariable(name string, value func() string) blueprint.Variable {
} }
var ( var (
// These variables are the only configuration needed by the boostrap // These variables are the only configuration needed by the bootstrap
// modules. // modules.
srcDir = bootstrapVariable("srcDir", func() string { srcDir = bootstrapVariable("srcDir", func() string {
return SrcDir return SrcDir

View file

@ -114,6 +114,7 @@ type Context struct {
globs map[string]GlobPath globs map[string]GlobPath
globLock sync.Mutex globLock sync.Mutex
srcDir string
fs pathtools.FileSystem fs pathtools.FileSystem
moduleListFile string moduleListFile string
} }
@ -448,6 +449,15 @@ func (c *Context) SetNameInterface(i NameInterface) {
c.nameInterface = i c.nameInterface = i
} }
func (c *Context) SetSrcDir(path string) {
c.srcDir = path
c.fs = pathtools.NewOsFs(path)
}
func (c *Context) SrcDir() string {
return c.srcDir
}
func singletonPkgPath(singleton Singleton) string { func singletonPkgPath(singleton Singleton) string {
typ := reflect.TypeOf(singleton) typ := reflect.TypeOf(singleton)
for typ.Kind() == reflect.Ptr { for typ.Kind() == reflect.Ptr {

View file

@ -36,7 +36,7 @@ const (
DontFollowSymlinks = ShouldFollowSymlinks(false) DontFollowSymlinks = ShouldFollowSymlinks(false)
) )
var OsFs FileSystem = osFs{} var OsFs FileSystem = &osFs{}
func MockFs(files map[string][]byte) FileSystem { func MockFs(files map[string][]byte) FileSystem {
fs := &mockFs{ fs := &mockFs{
@ -123,11 +123,45 @@ type FileSystem interface {
} }
// osFs implements FileSystem using the local disk. // osFs implements FileSystem using the local disk.
type osFs struct{} type osFs struct {
srcDir string
}
func (osFs) Open(name string) (ReaderAtSeekerCloser, error) { return os.Open(name) } func NewOsFs(path string) FileSystem {
func (osFs) Exists(name string) (bool, bool, error) { return &osFs{srcDir: path}
stat, err := os.Stat(name) }
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(filepath.Join(fs.srcDir, name))
}
func (fs *osFs) Exists(name string) (bool, bool, error) {
stat, err := os.Stat(filepath.Join(fs.srcDir, name))
if err == nil { if err == nil {
return true, stat.IsDir(), nil return true, stat.IsDir(), nil
} else if os.IsNotExist(err) { } else if os.IsNotExist(err) {
@ -137,45 +171,47 @@ func (osFs) Exists(name string) (bool, bool, error) {
} }
} }
func (osFs) IsDir(name string) (bool, error) { func (fs *osFs) IsDir(name string) (bool, error) {
info, err := os.Stat(name) info, err := os.Stat(filepath.Join(fs.srcDir, name))
if err != nil { if err != nil {
return false, err return false, err
} }
return info.IsDir(), nil return info.IsDir(), nil
} }
func (osFs) IsSymlink(name string) (bool, error) { func (fs *osFs) IsSymlink(name string) (bool, error) {
if info, err := os.Lstat(name); err != nil { if info, err := os.Lstat(filepath.Join(fs.srcDir, name)); err != nil {
return false, err return false, err
} else { } else {
return info.Mode()&os.ModeSymlink != 0, nil return info.Mode()&os.ModeSymlink != 0, nil
} }
} }
func (fs osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) { func (fs *osFs) Glob(pattern string, excludes []string, follow ShouldFollowSymlinks) (matches, dirs []string, err error) {
return startGlob(fs, pattern, excludes, follow) return startGlob(fs, pattern, excludes, follow)
} }
func (osFs) glob(pattern string) ([]string, error) { func (fs *osFs) glob(pattern string) ([]string, error) {
return filepath.Glob(pattern) paths, err := filepath.Glob(filepath.Join(fs.srcDir, pattern))
fs.removeSrcDirPrefixes(paths)
return paths, err
} }
func (osFs) Lstat(path string) (stats os.FileInfo, err error) { func (fs *osFs) Lstat(path string) (stats os.FileInfo, err error) {
return os.Lstat(path) return os.Lstat(filepath.Join(fs.srcDir, path))
} }
func (osFs) Stat(path string) (stats os.FileInfo, err error) { func (fs *osFs) Stat(path string) (stats os.FileInfo, err error) {
return os.Stat(path) return os.Stat(filepath.Join(fs.srcDir, path))
} }
// Returns a list of all directories under dir // Returns a list of all directories under dir
func (osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) { func (fs *osFs) ListDirsRecursive(name string, follow ShouldFollowSymlinks) (dirs []string, err error) {
return listDirsRecursive(OsFs, name, follow) return listDirsRecursive(fs, name, follow)
} }
func (osFs) ReadDirNames(name string) ([]string, error) { func (fs *osFs) ReadDirNames(name string) ([]string, error) {
dir, err := os.Open(name) dir, err := os.Open(filepath.Join(fs.srcDir, name))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -190,8 +226,8 @@ func (osFs) ReadDirNames(name string) ([]string, error) {
return contents, nil return contents, nil
} }
func (osFs) Readlink(name string) (string, error) { func (fs *osFs) Readlink(name string) (string, error) {
return os.Readlink(name) return os.Readlink(filepath.Join(fs.srcDir, name))
} }
type mockFs struct { type mockFs struct {

View file

@ -22,6 +22,8 @@ import (
"testing" "testing"
) )
const testdataDir = "testdata/dangling"
func symlinkMockFs() *mockFs { func symlinkMockFs() *mockFs {
files := []string{ files := []string{
"a/a/a", "a/a/a",
@ -149,25 +151,37 @@ func TestFs_IsDir(t *testing.T) {
} }
mock := symlinkMockFs() mock := symlinkMockFs()
fsList := []FileSystem{mock, OsFs}
names := []string{"mock", "os"}
os.Chdir("testdata/dangling") run := func(t *testing.T, fs FileSystem) {
defer os.Chdir("../..") for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
for i, fs := range fsList { got, err := fs.IsDir(test.name)
t.Run(names[i], func(t *testing.T) { checkErr(t, test.err, err)
for _, test := range testCases { if got != test.isDir {
t.Run(test.name, func(t *testing.T) { t.Errorf("want: %v, got %v", test.isDir, got)
got, err := fs.IsDir(test.name) }
checkErr(t, test.err, err) })
if got != test.isDir { }
t.Errorf("want: %v, got %v", test.isDir, got)
}
})
}
})
} }
t.Run("mock", func(t *testing.T) {
run(t, mock)
})
t.Run("os", func(t *testing.T) {
os.Chdir("testdata/dangling")
defer os.Chdir("../..")
run(t, OsFs)
})
t.Run("os relative srcDir", func(t *testing.T) {
run(t, NewOsFs(testdataDir))
})
t.Run("os absolute srcDir", func(t *testing.T) {
wd, _ := os.Getwd()
run(t, NewOsFs(filepath.Join(wd, testdataDir)))
})
} }
func TestFs_ListDirsRecursiveFollowSymlinks(t *testing.T) { func TestFs_ListDirsRecursiveFollowSymlinks(t *testing.T) {
@ -200,26 +214,37 @@ func TestFs_ListDirsRecursiveFollowSymlinks(t *testing.T) {
} }
mock := symlinkMockFs() mock := symlinkMockFs()
fsList := []FileSystem{mock, OsFs}
names := []string{"mock", "os"}
os.Chdir("testdata/dangling") run := func(t *testing.T, fs FileSystem) {
defer os.Chdir("../..") for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
for i, fs := range fsList { got, err := fs.ListDirsRecursive(test.name, FollowSymlinks)
t.Run(names[i], func(t *testing.T) { checkErr(t, test.err, err)
if !reflect.DeepEqual(got, test.dirs) {
for _, test := range testCases { t.Errorf("want: %v, got %v", test.dirs, got)
t.Run(test.name, func(t *testing.T) { }
got, err := fs.ListDirsRecursive(test.name, FollowSymlinks) })
checkErr(t, test.err, err) }
if !reflect.DeepEqual(got, test.dirs) {
t.Errorf("want: %v, got %v", test.dirs, got)
}
})
}
})
} }
t.Run("mock", func(t *testing.T) {
run(t, mock)
})
t.Run("os", func(t *testing.T) {
os.Chdir("testdata/dangling")
defer os.Chdir("../..")
run(t, OsFs)
})
t.Run("os relative srcDir", func(t *testing.T) {
run(t, NewOsFs(testdataDir))
})
t.Run("os absolute srcDir", func(t *testing.T) {
wd, _ := os.Getwd()
run(t, NewOsFs(filepath.Join(wd, testdataDir)))
})
} }
func TestFs_ListDirsRecursiveDontFollowSymlinks(t *testing.T) { func TestFs_ListDirsRecursiveDontFollowSymlinks(t *testing.T) {
@ -252,26 +277,37 @@ func TestFs_ListDirsRecursiveDontFollowSymlinks(t *testing.T) {
} }
mock := symlinkMockFs() mock := symlinkMockFs()
fsList := []FileSystem{mock, OsFs}
names := []string{"mock", "os"}
os.Chdir("testdata/dangling") run := func(t *testing.T, fs FileSystem) {
defer os.Chdir("../..") for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
for i, fs := range fsList { got, err := fs.ListDirsRecursive(test.name, DontFollowSymlinks)
t.Run(names[i], func(t *testing.T) { checkErr(t, test.err, err)
if !reflect.DeepEqual(got, test.dirs) {
for _, test := range testCases { t.Errorf("want: %v, got %v", test.dirs, got)
t.Run(test.name, func(t *testing.T) { }
got, err := fs.ListDirsRecursive(test.name, DontFollowSymlinks) })
checkErr(t, test.err, err) }
if !reflect.DeepEqual(got, test.dirs) {
t.Errorf("want: %v, got %v", test.dirs, got)
}
})
}
})
} }
t.Run("mock", func(t *testing.T) {
run(t, mock)
})
t.Run("os", func(t *testing.T) {
os.Chdir("testdata/dangling")
defer os.Chdir("../..")
run(t, OsFs)
})
t.Run("os relative srcDir", func(t *testing.T) {
run(t, NewOsFs(testdataDir))
})
t.Run("os absolute srcDir", func(t *testing.T) {
wd, _ := os.Getwd()
run(t, NewOsFs(filepath.Join(wd, testdataDir)))
})
} }
func TestFs_Readlink(t *testing.T) { func TestFs_Readlink(t *testing.T) {
@ -321,26 +357,37 @@ func TestFs_Readlink(t *testing.T) {
} }
mock := symlinkMockFs() mock := symlinkMockFs()
fsList := []FileSystem{mock, OsFs}
names := []string{"mock", "os"}
os.Chdir("testdata/dangling") run := func(t *testing.T, fs FileSystem) {
defer os.Chdir("../..") for _, test := range testCases {
t.Run(test.from, func(t *testing.T) {
for i, fs := range fsList { got, err := fs.Readlink(test.from)
t.Run(names[i], func(t *testing.T) { checkErr(t, test.err, err)
if got != test.to {
for _, test := range testCases { t.Errorf("fs.Readlink(%q) want: %q, got %q", test.from, test.to, got)
t.Run(test.from, func(t *testing.T) { }
got, err := fs.Readlink(test.from) })
checkErr(t, test.err, err) }
if got != test.to {
t.Errorf("fs.Readlink(%q) want: %q, got %q", test.from, test.to, got)
}
})
}
})
} }
t.Run("mock", func(t *testing.T) {
run(t, mock)
})
t.Run("os", func(t *testing.T) {
os.Chdir("testdata/dangling")
defer os.Chdir("../..")
run(t, OsFs)
})
t.Run("os relative srcDir", func(t *testing.T) {
run(t, NewOsFs(testdataDir))
})
t.Run("os absolute srcDir", func(t *testing.T) {
wd, _ := os.Getwd()
run(t, NewOsFs(filepath.Join(wd, testdataDir)))
})
} }
func TestFs_Lstat(t *testing.T) { func TestFs_Lstat(t *testing.T) {
@ -392,33 +439,44 @@ func TestFs_Lstat(t *testing.T) {
} }
mock := symlinkMockFs() mock := symlinkMockFs()
fsList := []FileSystem{mock, OsFs}
names := []string{"mock", "os"}
os.Chdir("testdata/dangling") run := func(t *testing.T, fs FileSystem) {
defer os.Chdir("../..") for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
for i, fs := range fsList { got, err := fs.Lstat(test.name)
t.Run(names[i], func(t *testing.T) { checkErr(t, test.err, err)
if err != nil {
for _, test := range testCases { return
t.Run(test.name, func(t *testing.T) { }
got, err := fs.Lstat(test.name) if got.Mode()&os.ModeType != test.mode {
checkErr(t, test.err, err) t.Errorf("fs.Lstat(%q).Mode()&os.ModeType want: %x, got %x",
if err != nil { test.name, test.mode, got.Mode()&os.ModeType)
return }
} if test.mode == 0 && got.Size() != test.size {
if got.Mode()&os.ModeType != test.mode { t.Errorf("fs.Lstat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
t.Errorf("fs.Lstat(%q).Mode()&os.ModeType want: %x, got %x", }
test.name, test.mode, got.Mode()&os.ModeType) })
} }
if test.mode == 0 && got.Size() != test.size {
t.Errorf("fs.Lstat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
}
})
}
})
} }
t.Run("mock", func(t *testing.T) {
run(t, mock)
})
t.Run("os", func(t *testing.T) {
os.Chdir("testdata/dangling")
defer os.Chdir("../..")
run(t, OsFs)
})
t.Run("os relative srcDir", func(t *testing.T) {
run(t, NewOsFs(testdataDir))
})
t.Run("os absolute srcDir", func(t *testing.T) {
wd, _ := os.Getwd()
run(t, NewOsFs(filepath.Join(wd, testdataDir)))
})
} }
func TestFs_Stat(t *testing.T) { func TestFs_Stat(t *testing.T) {
@ -470,33 +528,44 @@ func TestFs_Stat(t *testing.T) {
} }
mock := symlinkMockFs() mock := symlinkMockFs()
fsList := []FileSystem{mock, OsFs}
names := []string{"mock", "os"}
os.Chdir("testdata/dangling") run := func(t *testing.T, fs FileSystem) {
defer os.Chdir("../..") for _, test := range testCases {
t.Run(test.name, func(t *testing.T) {
for i, fs := range fsList { got, err := fs.Stat(test.name)
t.Run(names[i], func(t *testing.T) { checkErr(t, test.err, err)
if err != nil {
for _, test := range testCases { return
t.Run(test.name, func(t *testing.T) { }
got, err := fs.Stat(test.name) if got.Mode()&os.ModeType != test.mode {
checkErr(t, test.err, err) t.Errorf("fs.Stat(%q).Mode()&os.ModeType want: %x, got %x",
if err != nil { test.name, test.mode, got.Mode()&os.ModeType)
return }
} if test.mode == 0 && got.Size() != test.size {
if got.Mode()&os.ModeType != test.mode { t.Errorf("fs.Stat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
t.Errorf("fs.Stat(%q).Mode()&os.ModeType want: %x, got %x", }
test.name, test.mode, got.Mode()&os.ModeType) })
} }
if test.mode == 0 && got.Size() != test.size {
t.Errorf("fs.Stat(%q).Size() want: %d, got %d", test.name, test.size, got.Size())
}
})
}
})
} }
t.Run("mock", func(t *testing.T) {
run(t, mock)
})
t.Run("os", func(t *testing.T) {
os.Chdir("testdata/dangling")
defer os.Chdir("../..")
run(t, OsFs)
})
t.Run("os relative srcDir", func(t *testing.T) {
run(t, NewOsFs(testdataDir))
})
t.Run("os absolute srcDir", func(t *testing.T) {
wd, _ := os.Getwd()
run(t, NewOsFs(filepath.Join(wd, testdataDir)))
})
} }
func TestMockFs_glob(t *testing.T) { func TestMockFs_glob(t *testing.T) {
@ -538,27 +607,39 @@ func TestMockFs_glob(t *testing.T) {
} }
mock := symlinkMockFs() mock := symlinkMockFs()
fsList := []FileSystem{mock, OsFs}
names := []string{"mock", "os"}
os.Chdir("testdata/dangling") run := func(t *testing.T, fs FileSystem) {
defer os.Chdir("../..") for _, test := range testCases {
t.Run(test.pattern, func(t *testing.T) {
for i, fs := range fsList { got, err := fs.glob(test.pattern)
t.Run(names[i], func(t *testing.T) { if err != nil {
for _, test := range testCases { t.Fatal(err)
t.Run(test.pattern, func(t *testing.T) { }
got, err := fs.glob(test.pattern) if !reflect.DeepEqual(got, test.files) {
if err != nil { t.Errorf("want: %v, got %v", test.files, got)
t.Fatal(err) }
} })
if !reflect.DeepEqual(got, test.files) { }
t.Errorf("want: %v, got %v", test.files, got)
}
})
}
})
} }
t.Run("mock", func(t *testing.T) {
run(t, mock)
})
t.Run("os", func(t *testing.T) {
os.Chdir("testdata/dangling")
defer os.Chdir("../..")
run(t, OsFs)
})
t.Run("os relative srcDir", func(t *testing.T) {
run(t, NewOsFs(testdataDir))
})
t.Run("os absolute srcDir", func(t *testing.T) {
wd, _ := os.Getwd()
run(t, NewOsFs(filepath.Join(wd, testdataDir)))
})
} }
func syscallError(err error) error { func syscallError(err error) error {