1*105f6285SAndroid Build Coastguard Worker// Copyright 2020 Google LLC 2*105f6285SAndroid Build Coastguard Worker// 3*105f6285SAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); 4*105f6285SAndroid Build Coastguard Worker// you may not use this file except in compliance with the License. 5*105f6285SAndroid Build Coastguard Worker// You may obtain a copy of the License at 6*105f6285SAndroid Build Coastguard Worker// 7*105f6285SAndroid Build Coastguard Worker// https://www.apache.org/licenses/LICENSE-2.0 8*105f6285SAndroid Build Coastguard Worker// 9*105f6285SAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*105f6285SAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, 11*105f6285SAndroid Build Coastguard Worker// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12*105f6285SAndroid Build Coastguard Worker// See the License for the specific language governing permissions and 13*105f6285SAndroid Build Coastguard Worker// limitations under the License. 14*105f6285SAndroid Build Coastguard Worker 15*105f6285SAndroid Build Coastguard Workerpackage workspace 16*105f6285SAndroid Build Coastguard Worker 17*105f6285SAndroid Build Coastguard Workerimport ( 18*105f6285SAndroid Build Coastguard Worker "fmt" 19*105f6285SAndroid Build Coastguard Worker "io" 20*105f6285SAndroid Build Coastguard Worker "io/ioutil" 21*105f6285SAndroid Build Coastguard Worker "os" 22*105f6285SAndroid Build Coastguard Worker "path/filepath" 23*105f6285SAndroid Build Coastguard Worker "strings" 24*105f6285SAndroid Build Coastguard Worker) 25*105f6285SAndroid Build Coastguard Worker 26*105f6285SAndroid Build Coastguard Workertype FileCopier struct { 27*105f6285SAndroid Build Coastguard Worker} 28*105f6285SAndroid Build Coastguard Worker 29*105f6285SAndroid Build Coastguard Workerfunc NewFileCopier() *FileCopier { 30*105f6285SAndroid Build Coastguard Worker var f FileCopier 31*105f6285SAndroid Build Coastguard Worker return &f 32*105f6285SAndroid Build Coastguard Worker} 33*105f6285SAndroid Build Coastguard Worker 34*105f6285SAndroid Build Coastguard Workerfunc (f FileCopier) GetIsGitProjectFunc(codebaseDir string, gitProjects []string) func(string) (bool, error) { 35*105f6285SAndroid Build Coastguard Worker //Convert the git project list to a set to speed up lookups 36*105f6285SAndroid Build Coastguard Worker gitProjectSet := make(map[string]struct{}) 37*105f6285SAndroid Build Coastguard Worker var exists = struct{}{} 38*105f6285SAndroid Build Coastguard Worker for _, project := range gitProjects { 39*105f6285SAndroid Build Coastguard Worker gitProjectSet[project] = exists 40*105f6285SAndroid Build Coastguard Worker } 41*105f6285SAndroid Build Coastguard Worker 42*105f6285SAndroid Build Coastguard Worker return func(pathToCheck string) (bool, error) { 43*105f6285SAndroid Build Coastguard Worker var err error 44*105f6285SAndroid Build Coastguard Worker if pathToCheck, err = filepath.Rel(codebaseDir, pathToCheck); err != nil { 45*105f6285SAndroid Build Coastguard Worker return false, err 46*105f6285SAndroid Build Coastguard Worker } 47*105f6285SAndroid Build Coastguard Worker if _, ok := gitProjectSet[pathToCheck]; ok { 48*105f6285SAndroid Build Coastguard Worker return true, err 49*105f6285SAndroid Build Coastguard Worker } 50*105f6285SAndroid Build Coastguard Worker return false, err 51*105f6285SAndroid Build Coastguard Worker } 52*105f6285SAndroid Build Coastguard Worker} 53*105f6285SAndroid Build Coastguard Worker 54*105f6285SAndroid Build Coastguard Workerfunc (f FileCopier) GetContainsGitProjectFunc(codebaseDir string, gitProjects []string) func(string) (bool, error) { 55*105f6285SAndroid Build Coastguard Worker //Extract the set of dirs that contain git projects 56*105f6285SAndroid Build Coastguard Worker containsGitSet := make(map[string]struct{}) 57*105f6285SAndroid Build Coastguard Worker var exists = struct{}{} 58*105f6285SAndroid Build Coastguard Worker for _, project := range gitProjects { 59*105f6285SAndroid Build Coastguard Worker for dir := project; dir != "." && dir != "/"; dir = filepath.Dir(dir) { 60*105f6285SAndroid Build Coastguard Worker containsGitSet[dir] = exists 61*105f6285SAndroid Build Coastguard Worker } 62*105f6285SAndroid Build Coastguard Worker } 63*105f6285SAndroid Build Coastguard Worker 64*105f6285SAndroid Build Coastguard Worker return func(pathToCheck string) (bool, error) { 65*105f6285SAndroid Build Coastguard Worker var err error 66*105f6285SAndroid Build Coastguard Worker if pathToCheck, err = filepath.Rel(codebaseDir, pathToCheck); err != nil { 67*105f6285SAndroid Build Coastguard Worker return false, err 68*105f6285SAndroid Build Coastguard Worker } 69*105f6285SAndroid Build Coastguard Worker if _, ok := containsGitSet[pathToCheck]; ok { 70*105f6285SAndroid Build Coastguard Worker return true, err 71*105f6285SAndroid Build Coastguard Worker } 72*105f6285SAndroid Build Coastguard Worker return false, err 73*105f6285SAndroid Build Coastguard Worker } 74*105f6285SAndroid Build Coastguard Worker} 75*105f6285SAndroid Build Coastguard Worker 76*105f6285SAndroid Build Coastguard Worker//gitProjects is relative to codebaseDir 77*105f6285SAndroid Build Coastguard Workerfunc (f FileCopier) Copy(codebaseDir string, gitProjects []string, workspaceDir string) error { 78*105f6285SAndroid Build Coastguard Worker isGitProject := f.GetIsGitProjectFunc(codebaseDir, gitProjects) 79*105f6285SAndroid Build Coastguard Worker containsGitProject := f.GetContainsGitProjectFunc(codebaseDir, gitProjects) 80*105f6285SAndroid Build Coastguard Worker 81*105f6285SAndroid Build Coastguard Worker return filepath.Walk(codebaseDir, 82*105f6285SAndroid Build Coastguard Worker func(path string, info os.FileInfo, err error) error { 83*105f6285SAndroid Build Coastguard Worker if err != nil { 84*105f6285SAndroid Build Coastguard Worker return err 85*105f6285SAndroid Build Coastguard Worker } 86*105f6285SAndroid Build Coastguard Worker 87*105f6285SAndroid Build Coastguard Worker // Copy files 88*105f6285SAndroid Build Coastguard Worker if !info.IsDir() { 89*105f6285SAndroid Build Coastguard Worker return f.CopyNode(info, codebaseDir, path, workspaceDir) 90*105f6285SAndroid Build Coastguard Worker } 91*105f6285SAndroid Build Coastguard Worker 92*105f6285SAndroid Build Coastguard Worker if path == filepath.Clean(codebaseDir) { 93*105f6285SAndroid Build Coastguard Worker return nil 94*105f6285SAndroid Build Coastguard Worker } 95*105f6285SAndroid Build Coastguard Worker 96*105f6285SAndroid Build Coastguard Worker // Always skip traversal of root repo directories 97*105f6285SAndroid Build Coastguard Worker if path == filepath.Join(codebaseDir, ".repo") { 98*105f6285SAndroid Build Coastguard Worker return filepath.SkipDir 99*105f6285SAndroid Build Coastguard Worker } 100*105f6285SAndroid Build Coastguard Worker 101*105f6285SAndroid Build Coastguard Worker // Skip all git projects 102*105f6285SAndroid Build Coastguard Worker var isGitProj bool 103*105f6285SAndroid Build Coastguard Worker if isGitProj, err = isGitProject(path); err != nil { 104*105f6285SAndroid Build Coastguard Worker return err 105*105f6285SAndroid Build Coastguard Worker } 106*105f6285SAndroid Build Coastguard Worker if isGitProj { 107*105f6285SAndroid Build Coastguard Worker return filepath.SkipDir 108*105f6285SAndroid Build Coastguard Worker } 109*105f6285SAndroid Build Coastguard Worker 110*105f6285SAndroid Build Coastguard Worker // Copy over files 111*105f6285SAndroid Build Coastguard Worker var containsGitProj bool 112*105f6285SAndroid Build Coastguard Worker if containsGitProj, err = containsGitProject(path); err != nil { 113*105f6285SAndroid Build Coastguard Worker return err 114*105f6285SAndroid Build Coastguard Worker } 115*105f6285SAndroid Build Coastguard Worker if !containsGitProj { 116*105f6285SAndroid Build Coastguard Worker destPath, err := f.GetDestPath(codebaseDir, path, workspaceDir) 117*105f6285SAndroid Build Coastguard Worker if err != nil { 118*105f6285SAndroid Build Coastguard Worker return err 119*105f6285SAndroid Build Coastguard Worker } 120*105f6285SAndroid Build Coastguard Worker if err = f.CopyDirRecursive(info, path, destPath); err != nil { 121*105f6285SAndroid Build Coastguard Worker return err 122*105f6285SAndroid Build Coastguard Worker } 123*105f6285SAndroid Build Coastguard Worker return filepath.SkipDir 124*105f6285SAndroid Build Coastguard Worker } 125*105f6285SAndroid Build Coastguard Worker return f.CopyNode(info, codebaseDir, path, workspaceDir) 126*105f6285SAndroid Build Coastguard Worker }) 127*105f6285SAndroid Build Coastguard Worker} 128*105f6285SAndroid Build Coastguard Worker 129*105f6285SAndroid Build Coastguard Workerfunc (f FileCopier) GetDestPath(codebaseDir, sourcePath, workspaceDir string) (string, error) { 130*105f6285SAndroid Build Coastguard Worker if !strings.HasPrefix(sourcePath+"/", codebaseDir+"/") { 131*105f6285SAndroid Build Coastguard Worker return "", fmt.Errorf("%s is not contained in %s", sourcePath, codebaseDir) 132*105f6285SAndroid Build Coastguard Worker } 133*105f6285SAndroid Build Coastguard Worker relPath, err := filepath.Rel(codebaseDir, sourcePath) 134*105f6285SAndroid Build Coastguard Worker if err != nil { 135*105f6285SAndroid Build Coastguard Worker return "", err 136*105f6285SAndroid Build Coastguard Worker } 137*105f6285SAndroid Build Coastguard Worker destPath := filepath.Join(workspaceDir, relPath) 138*105f6285SAndroid Build Coastguard Worker return destPath, err 139*105f6285SAndroid Build Coastguard Worker} 140*105f6285SAndroid Build Coastguard Worker 141*105f6285SAndroid Build Coastguard Worker// Copy any single file, symlink or dir non-recursively 142*105f6285SAndroid Build Coastguard Worker// sourcePath must be contained in codebaseDir 143*105f6285SAndroid Build Coastguard Workerfunc (f FileCopier) CopyNode(sourceInfo os.FileInfo, codebaseDir, sourcePath, workspaceDir string) error { 144*105f6285SAndroid Build Coastguard Worker destPath, err := f.GetDestPath(codebaseDir, sourcePath, workspaceDir) 145*105f6285SAndroid Build Coastguard Worker if err != nil { 146*105f6285SAndroid Build Coastguard Worker return err 147*105f6285SAndroid Build Coastguard Worker } 148*105f6285SAndroid Build Coastguard Worker switch { 149*105f6285SAndroid Build Coastguard Worker case sourceInfo.Mode()&os.ModeSymlink == os.ModeSymlink: 150*105f6285SAndroid Build Coastguard Worker return f.CopySymlink(sourcePath, destPath) 151*105f6285SAndroid Build Coastguard Worker case sourceInfo.Mode().IsDir(): 152*105f6285SAndroid Build Coastguard Worker return f.CopyDirOnly(sourceInfo, destPath) 153*105f6285SAndroid Build Coastguard Worker default: 154*105f6285SAndroid Build Coastguard Worker return f.CopyFile(sourceInfo, sourcePath, destPath) 155*105f6285SAndroid Build Coastguard Worker } 156*105f6285SAndroid Build Coastguard Worker} 157*105f6285SAndroid Build Coastguard Worker 158*105f6285SAndroid Build Coastguard Workerfunc (f FileCopier) CopySymlink(sourcePath string, destPath string) error { 159*105f6285SAndroid Build Coastguard Worker // Skip symlink if it already exists at the destination 160*105f6285SAndroid Build Coastguard Worker _, err := os.Lstat(destPath) 161*105f6285SAndroid Build Coastguard Worker if err == nil { 162*105f6285SAndroid Build Coastguard Worker return nil 163*105f6285SAndroid Build Coastguard Worker } 164*105f6285SAndroid Build Coastguard Worker 165*105f6285SAndroid Build Coastguard Worker target, err := os.Readlink(sourcePath) 166*105f6285SAndroid Build Coastguard Worker if err != nil { 167*105f6285SAndroid Build Coastguard Worker return err 168*105f6285SAndroid Build Coastguard Worker } 169*105f6285SAndroid Build Coastguard Worker 170*105f6285SAndroid Build Coastguard Worker return os.Symlink(target, destPath) 171*105f6285SAndroid Build Coastguard Worker} 172*105f6285SAndroid Build Coastguard Worker 173*105f6285SAndroid Build Coastguard Worker// CopyDirOnly copies a directory non-recursively 174*105f6285SAndroid Build Coastguard Worker// sourcePath must be contained in codebaseDir 175*105f6285SAndroid Build Coastguard Workerfunc (f FileCopier) CopyDirOnly(sourceInfo os.FileInfo, destPath string) error { 176*105f6285SAndroid Build Coastguard Worker _, err := os.Stat(destPath) 177*105f6285SAndroid Build Coastguard Worker if err == nil { 178*105f6285SAndroid Build Coastguard Worker // Dir already exists, nothing to do 179*105f6285SAndroid Build Coastguard Worker return err 180*105f6285SAndroid Build Coastguard Worker } else if os.IsNotExist(err) { 181*105f6285SAndroid Build Coastguard Worker return os.Mkdir(destPath, sourceInfo.Mode()) 182*105f6285SAndroid Build Coastguard Worker } 183*105f6285SAndroid Build Coastguard Worker return err 184*105f6285SAndroid Build Coastguard Worker} 185*105f6285SAndroid Build Coastguard Worker 186*105f6285SAndroid Build Coastguard Worker// CopyFile copies a single file 187*105f6285SAndroid Build Coastguard Worker// sourcePath must be contained in codebaseDir 188*105f6285SAndroid Build Coastguard Workerfunc (f FileCopier) CopyFile(sourceInfo os.FileInfo, sourcePath, destPath string) error { 189*105f6285SAndroid Build Coastguard Worker //Skip file if it already exists at the destination 190*105f6285SAndroid Build Coastguard Worker _, err := os.Lstat(destPath) 191*105f6285SAndroid Build Coastguard Worker if err == nil { 192*105f6285SAndroid Build Coastguard Worker return nil 193*105f6285SAndroid Build Coastguard Worker } 194*105f6285SAndroid Build Coastguard Worker 195*105f6285SAndroid Build Coastguard Worker sourceFile, err := os.Open(sourcePath) 196*105f6285SAndroid Build Coastguard Worker if err != nil { 197*105f6285SAndroid Build Coastguard Worker return err 198*105f6285SAndroid Build Coastguard Worker } 199*105f6285SAndroid Build Coastguard Worker defer sourceFile.Close() 200*105f6285SAndroid Build Coastguard Worker 201*105f6285SAndroid Build Coastguard Worker destFile, err := os.Create(destPath) 202*105f6285SAndroid Build Coastguard Worker if err != nil { 203*105f6285SAndroid Build Coastguard Worker return err 204*105f6285SAndroid Build Coastguard Worker } 205*105f6285SAndroid Build Coastguard Worker defer destFile.Close() 206*105f6285SAndroid Build Coastguard Worker 207*105f6285SAndroid Build Coastguard Worker _, err = io.Copy(destFile, sourceFile) 208*105f6285SAndroid Build Coastguard Worker if err != nil { 209*105f6285SAndroid Build Coastguard Worker return err 210*105f6285SAndroid Build Coastguard Worker } 211*105f6285SAndroid Build Coastguard Worker return os.Chmod(destPath, sourceInfo.Mode()) 212*105f6285SAndroid Build Coastguard Worker} 213*105f6285SAndroid Build Coastguard Worker 214*105f6285SAndroid Build Coastguard Workerfunc (f FileCopier) CopyDirRecursive(sourceInfo os.FileInfo, sourcePath, destPath string) error { 215*105f6285SAndroid Build Coastguard Worker if err := f.CopyDirOnly(sourceInfo, destPath); err != nil { 216*105f6285SAndroid Build Coastguard Worker return err 217*105f6285SAndroid Build Coastguard Worker } 218*105f6285SAndroid Build Coastguard Worker childNodes, err := ioutil.ReadDir(sourcePath) 219*105f6285SAndroid Build Coastguard Worker if err != nil { 220*105f6285SAndroid Build Coastguard Worker return err 221*105f6285SAndroid Build Coastguard Worker } 222*105f6285SAndroid Build Coastguard Worker for _, childInfo := range childNodes { 223*105f6285SAndroid Build Coastguard Worker childSourcePath := filepath.Join(sourcePath, childInfo.Name()) 224*105f6285SAndroid Build Coastguard Worker childDestPath := filepath.Join(destPath, childInfo.Name()) 225*105f6285SAndroid Build Coastguard Worker switch { 226*105f6285SAndroid Build Coastguard Worker case childInfo.Mode()&os.ModeSymlink == os.ModeSymlink: 227*105f6285SAndroid Build Coastguard Worker if err = f.CopySymlink(childSourcePath, childDestPath); err != nil { 228*105f6285SAndroid Build Coastguard Worker return err 229*105f6285SAndroid Build Coastguard Worker } 230*105f6285SAndroid Build Coastguard Worker case childInfo.Mode().IsDir(): 231*105f6285SAndroid Build Coastguard Worker if err = f.CopyDirRecursive(childInfo, childSourcePath, childDestPath); err != nil { 232*105f6285SAndroid Build Coastguard Worker return err 233*105f6285SAndroid Build Coastguard Worker } 234*105f6285SAndroid Build Coastguard Worker default: 235*105f6285SAndroid Build Coastguard Worker if err = f.CopyFile(childInfo, childSourcePath, childDestPath); err != nil { 236*105f6285SAndroid Build Coastguard Worker return err 237*105f6285SAndroid Build Coastguard Worker } 238*105f6285SAndroid Build Coastguard Worker } 239*105f6285SAndroid Build Coastguard Worker } 240*105f6285SAndroid Build Coastguard Worker return err 241*105f6285SAndroid Build Coastguard Worker} 242