xref: /aosp_15_r20/tools/treble/hacksaw/workspace/copier.go (revision 105f628577ac4ba0e277a494fbb614ed8c12a994)
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