blob: e3878b16c1454fea969d4bedd814ff692d792283 [file] [log] [blame]
Cole Faust324a92e2022-08-23 15:29:05 -07001// Copyright 2022 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020015package bp2build
16
17import (
18 "fmt"
19 "io/ioutil"
20 "os"
21 "path/filepath"
Cole Faust324a92e2022-08-23 15:29:05 -070022 "regexp"
Cole Faust358ba4f2023-01-18 15:00:42 -080023 "sort"
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +000024 "sync"
25 "sync/atomic"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020026
27 "android/soong/shared"
Cole Faust3f4f5212023-04-04 15:12:22 -070028
Chris Parsons1a12d032023-02-06 22:37:41 -050029 "github.com/google/blueprint/pathtools"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020030)
31
32// A tree structure that describes what to do at each directory in the created
33// symlink tree. Currently it is used to enumerate which files/directories
34// should be excluded from symlinking. Each instance of "node" represents a file
35// or a directory. If excluded is true, then that file/directory should be
36// excluded from symlinking. Otherwise, the node is not excluded, but one of its
37// descendants is (otherwise the node in question would not exist)
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000038
39type instructionsNode struct {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020040 name string
41 excluded bool // If false, this is just an intermediate node
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000042 children map[string]*instructionsNode
43}
44
45type symlinkForestContext struct {
46 verbose bool
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +000047 topdir string // $TOPDIR
48
49 // State
Usta Shresthada15c612022-11-08 14:12:36 -050050 wg sync.WaitGroup
51 depCh chan string
52 mkdirCount atomic.Uint64
53 symlinkCount atomic.Uint64
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020054}
55
Usta Shresthadb46a9b2022-07-11 11:29:56 -040056// Ensures that the node for the given path exists in the tree and returns it.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000057func ensureNodeExists(root *instructionsNode, path string) *instructionsNode {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020058 if path == "" {
59 return root
60 }
61
62 if path[len(path)-1] == '/' {
63 path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
64 }
65
66 dir, base := filepath.Split(path)
67
68 // First compute the parent node...
69 dn := ensureNodeExists(root, dir)
70
71 // then create the requested node as its direct child, if needed.
72 if child, ok := dn.children[base]; ok {
73 return child
74 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000075 dn.children[base] = &instructionsNode{base, false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020076 return dn.children[base]
77 }
78}
79
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000080// Turns a list of paths to be excluded into a tree
81func instructionsFromExcludePathList(paths []string) *instructionsNode {
82 result := &instructionsNode{"", false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020083
84 for _, p := range paths {
85 ensureNodeExists(result, p).excluded = true
86 }
87
88 return result
89}
90
Cole Faust324a92e2022-08-23 15:29:05 -070091func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error {
92
93 srcBuildFileContent, err := os.ReadFile(srcBuildFile)
94 if err != nil {
95 return err
96 }
97
98 generatedBuildFileContent, err := os.ReadFile(generatedBuildFile)
99 if err != nil {
100 return err
101 }
102
103 // There can't be a package() call in both the source and generated BUILD files.
104 // bp2build will generate a package() call for licensing information, but if
105 // there's no licensing information, it will still generate a package() call
106 // that just sets default_visibility=public. If the handcrafted build file
107 // also has a package() call, we'll allow it to override the bp2build
108 // generated one if it doesn't have any licensing information. If the bp2build
109 // one has licensing information and the handcrafted one exists, we'll leave
110 // them both in for bazel to throw an error.
111 packageRegex := regexp.MustCompile(`(?m)^package\s*\(`)
112 packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`)
113 if packageRegex.Find(srcBuildFileContent) != nil {
114 if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil {
115 fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n",
116 generatedBuildFile, srcBuildFile)
117 }
118 generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
119 }
120
Chris Parsons1a12d032023-02-06 22:37:41 -0500121 newContents := generatedBuildFileContent
122 if newContents[len(newContents)-1] != '\n' {
123 newContents = append(newContents, '\n')
Cole Faust324a92e2022-08-23 15:29:05 -0700124 }
Chris Parsons1a12d032023-02-06 22:37:41 -0500125 newContents = append(newContents, srcBuildFileContent...)
Cole Faust324a92e2022-08-23 15:29:05 -0700126
Cole Faust3f4f5212023-04-04 15:12:22 -0700127 // Say you run bp2build 4 times:
128 // - The first time there's only an Android.bp file. bp2build will convert it to a build file
129 // under out/soong/bp2build, then symlink from the forest to that generated file
130 // - Then you add a handcrafted BUILD file in the same directory. bp2build will merge this with
131 // the generated one, and write the result to the output file in the forest. But the output
132 // file was a symlink to out/soong/bp2build from the previous step! So we erroneously update
133 // the file in out/soong/bp2build instead. So far this doesn't cause any problems...
134 // - You run a 3rd bp2build with no relevant changes. Everything continues to work.
135 // - You then add a comment to the handcrafted BUILD file. This causes a merge with the
136 // generated file again. But since we wrote to the generated file in step 2, the generated
137 // file has an old copy of the handcrafted file in it! This probably causes duplicate bazel
138 // targets.
139 // To solve this, if we see that the output file is a symlink from a previous build, remove it.
140 stat, err := os.Lstat(output)
141 if err != nil && !os.IsNotExist(err) {
142 return err
143 } else if err == nil {
144 if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
145 if verbose {
146 fmt.Fprintf(os.Stderr, "Removing symlink so that we can replace it with a merged file: %s\n", output)
147 }
148 err = os.Remove(output)
149 if err != nil {
150 return err
151 }
152 }
153 }
154
Chris Parsons1a12d032023-02-06 22:37:41 -0500155 return pathtools.WriteFileIfChanged(output, newContents, 0666)
Cole Faust324a92e2022-08-23 15:29:05 -0700156}
157
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200158// Calls readdir() and returns it as a map from the basename of the files in dir
159// to os.FileInfo.
160func readdirToMap(dir string) map[string]os.FileInfo {
161 entryList, err := ioutil.ReadDir(dir)
162 result := make(map[string]os.FileInfo)
163
164 if err != nil {
165 if os.IsNotExist(err) {
166 // It's okay if a directory doesn't exist; it just means that one of the
167 // trees to be merged contains parts the other doesn't
168 return result
169 } else {
170 fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
171 os.Exit(1)
172 }
173 }
174
175 for _, fi := range entryList {
176 result[fi.Name()] = fi
177 }
178
179 return result
180}
181
182// Creates a symbolic link at dst pointing to src
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500183func symlinkIntoForest(topdir, dst, src string) uint64 {
184 srcPath := shared.JoinPath(topdir, src)
185 dstPath := shared.JoinPath(topdir, dst)
186
187 // Check if a symlink already exists.
188 if dstInfo, err := os.Lstat(dstPath); err != nil {
189 if !os.IsNotExist(err) {
190 fmt.Fprintf(os.Stderr, "Failed to lstat '%s': %s", dst, err)
191 os.Exit(1)
192 }
193 } else {
194 if dstInfo.Mode()&os.ModeSymlink != 0 {
195 // Assume that the link's target is correct, i.e. no manual tampering.
196 // E.g. OUT_DIR could have been previously used with a different source tree check-out!
197 return 0
198 } else {
199 if err := os.RemoveAll(dstPath); err != nil {
200 fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", dst, err)
201 os.Exit(1)
202 }
203 }
204 }
205
206 // Create symlink.
207 if err := os.Symlink(srcPath, dstPath); err != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200208 fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
209 os.Exit(1)
210 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500211 return 1
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200212}
213
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200214func isDir(path string, fi os.FileInfo) bool {
215 if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
216 return fi.IsDir()
217 }
218
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000219 fi2, statErr := os.Stat(path)
220 if statErr == nil {
221 return fi2.IsDir()
222 }
223
224 // Check if this is a dangling symlink. If so, treat it like a file, not a dir.
225 _, lstatErr := os.Lstat(path)
226 if lstatErr != nil {
227 fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200228 os.Exit(1)
229 }
230
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000231 return false
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200232}
233
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200234// Recursively plants a symlink forest at forestDir. The symlink tree will
235// contain every file in buildFilesDir and srcDir excluding the files in
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000236// instructions. Collects every directory encountered during the traversal of
237// srcDir .
238func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *instructionsNode, forestDir string, buildFilesDir string, srcDir string) {
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000239 defer context.wg.Done()
240
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000241 if instructions != nil && instructions.excluded {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500242 // Excluded paths are skipped at the level of the non-excluded parent.
243 fmt.Fprintf(os.Stderr, "may not specify a root-level exclude directory '%s'", srcDir)
244 os.Exit(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200245 }
246
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000247 // We don't add buildFilesDir here because the bp2build files marker files is
248 // already a dependency which covers it. If we ever wanted to turn this into
249 // a generic symlink forest creation tool, we'd need to add it, too.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000250 context.depCh <- srcDir
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000251
252 srcDirMap := readdirToMap(shared.JoinPath(context.topdir, srcDir))
253 buildFilesMap := readdirToMap(shared.JoinPath(context.topdir, buildFilesDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200254
Cole Faust324a92e2022-08-23 15:29:05 -0700255 renamingBuildFile := false
256 if _, ok := srcDirMap["BUILD"]; ok {
257 if _, ok := srcDirMap["BUILD.bazel"]; !ok {
258 if _, ok := buildFilesMap["BUILD.bazel"]; ok {
259 renamingBuildFile = true
260 srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
261 delete(srcDirMap, "BUILD")
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500262 if instructions != nil {
263 if _, ok := instructions.children["BUILD"]; ok {
264 instructions.children["BUILD.bazel"] = instructions.children["BUILD"]
265 delete(instructions.children, "BUILD")
266 }
267 }
Cole Faust324a92e2022-08-23 15:29:05 -0700268 }
269 }
270 }
271
Cole Faust358ba4f2023-01-18 15:00:42 -0800272 allEntries := make([]string, 0, len(srcDirMap)+len(buildFilesMap))
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400273 for n := range srcDirMap {
Cole Faust358ba4f2023-01-18 15:00:42 -0800274 allEntries = append(allEntries, n)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200275 }
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400276 for n := range buildFilesMap {
Cole Faust358ba4f2023-01-18 15:00:42 -0800277 if _, ok := srcDirMap[n]; !ok {
278 allEntries = append(allEntries, n)
279 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200280 }
Cole Faust358ba4f2023-01-18 15:00:42 -0800281 // Tests read the error messages generated, so ensure their order is deterministic
282 sort.Strings(allEntries)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200283
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500284 fullForestPath := shared.JoinPath(context.topdir, forestDir)
285 createForestDir := false
286 if fi, err := os.Lstat(fullForestPath); err != nil {
287 if os.IsNotExist(err) {
288 createForestDir = true
289 } else {
290 fmt.Fprintf(os.Stderr, "Could not read info for '%s': %s\n", forestDir, err)
291 }
292 } else if fi.Mode()&os.ModeDir == 0 {
293 if err := os.RemoveAll(fullForestPath); err != nil {
294 fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", forestDir, err)
295 os.Exit(1)
296 }
297 createForestDir = true
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200298 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500299 if createForestDir {
300 if err := os.MkdirAll(fullForestPath, 0777); err != nil {
301 fmt.Fprintf(os.Stderr, "Could not mkdir '%s': %s\n", forestDir, err)
302 os.Exit(1)
303 }
304 context.mkdirCount.Add(1)
305 }
306
307 // Start with a list of items that already exist in the forest, and remove
308 // each element as it is processed in allEntries. Any remaining items in
309 // forestMapForDeletion must be removed. (This handles files which were
310 // removed since the previous forest generation).
311 forestMapForDeletion := readdirToMap(shared.JoinPath(context.topdir, forestDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200312
Cole Faust358ba4f2023-01-18 15:00:42 -0800313 for _, f := range allEntries {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200314 if f[0] == '.' {
315 continue // Ignore dotfiles
316 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500317 delete(forestMapForDeletion, f)
318 // todo add deletionCount metric
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200319
320 // The full paths of children in the input trees and in the output tree
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200321 forestChild := shared.JoinPath(forestDir, f)
322 srcChild := shared.JoinPath(srcDir, f)
Cole Faust324a92e2022-08-23 15:29:05 -0700323 if f == "BUILD.bazel" && renamingBuildFile {
324 srcChild = shared.JoinPath(srcDir, "BUILD")
325 }
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200326 buildFilesChild := shared.JoinPath(buildFilesDir, f)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200327
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000328 // Descend in the instruction tree if it exists
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500329 var instructionsChild *instructionsNode
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000330 if instructions != nil {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500331 instructionsChild = instructions.children[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200332 }
333
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200334 srcChildEntry, sExists := srcDirMap[f]
335 buildFilesChildEntry, bExists := buildFilesMap[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200336
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000337 if instructionsChild != nil && instructionsChild.excluded {
Cole Faust324a92e2022-08-23 15:29:05 -0700338 if bExists {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500339 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
Cole Faust324a92e2022-08-23 15:29:05 -0700340 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200341 continue
342 }
343
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000344 sDir := sExists && isDir(shared.JoinPath(context.topdir, srcChild), srcChildEntry)
345 bDir := bExists && isDir(shared.JoinPath(context.topdir, buildFilesChild), buildFilesChildEntry)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200346
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200347 if !sExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000348 if bDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200349 // Not in the source tree, but we have to exclude something from under
350 // this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000351 context.wg.Add(1)
352 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200353 } else {
354 // Not in the source tree, symlink BUILD file
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500355 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200356 }
357 } else if !bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000358 if sDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200359 // Not in the build file tree, but we have to exclude something from
360 // under this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000361 context.wg.Add(1)
362 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200363 } else {
364 // Not in the build file tree, symlink source tree, carry on
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500365 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, srcChild))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200366 }
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200367 } else if sDir && bDir {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200368 // Both are directories. Descend.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000369 context.wg.Add(1)
370 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200371 } else if !sDir && !bDir {
Cole Faust324a92e2022-08-23 15:29:05 -0700372 // Neither is a directory. Merge them.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000373 srcBuildFile := shared.JoinPath(context.topdir, srcChild)
374 generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild)
Usta Shrestha783ec5c2022-10-25 01:22:36 -0400375 // The Android.bp file that codegen used to produce `buildFilesChild` is
376 // already a dependency, we can ignore `buildFilesChild`.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000377 context.depCh <- srcChild
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500378 if err := mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose); err != nil {
Cole Faust324a92e2022-08-23 15:29:05 -0700379 fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
380 srcBuildFile, generatedBuildFile, err)
Usta Shrestha071f6c22022-10-25 12:34:06 -0400381 os.Exit(1)
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700382 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200383 } else {
384 // Both exist and one is a file. This is an error.
385 fmt.Fprintf(os.Stderr,
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200386 "Conflict in workspace symlink tree creation: both '%s' and '%s' exist and exactly one is a directory\n",
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200387 srcChild, buildFilesChild)
Usta Shrestha071f6c22022-10-25 12:34:06 -0400388 os.Exit(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200389 }
390 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200391
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500392 // Remove all files in the forest that exist in neither the source
393 // tree nor the build files tree. (This handles files which were removed
394 // since the previous forest generation).
395 for f := range forestMapForDeletion {
396 var instructionsChild *instructionsNode
397 if instructions != nil {
398 instructionsChild = instructions.children[f]
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000399 }
400
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500401 if instructionsChild != nil && instructionsChild.excluded {
402 // This directory may be excluded because bazel writes to it under the
403 // forest root. Thus this path is intentionally left alone.
404 continue
405 }
406 forestChild := shared.JoinPath(context.topdir, forestDir, f)
407 if err := os.RemoveAll(forestChild); err != nil {
408 fmt.Fprintf(os.Stderr, "Failed to remove '%s/%s': %s", forestDir, f, err)
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000409 os.Exit(1)
410 }
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000411 }
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000412}
413
Usta Shresthada15c612022-11-08 14:12:36 -0500414// PlantSymlinkForest Creates a symlink forest by merging the directory tree at "buildFiles" and
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200415// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
416// under srcDir on which readdir() had to be called to produce the symlink
417// forest.
Usta Shresthada15c612022-11-08 14:12:36 -0500418func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) (deps []string, mkdirCount, symlinkCount uint64) {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000419 context := &symlinkForestContext{
Usta Shresthada15c612022-11-08 14:12:36 -0500420 verbose: verbose,
421 topdir: topdir,
422 depCh: make(chan string),
423 mkdirCount: atomic.Uint64{},
424 symlinkCount: atomic.Uint64{},
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000425 }
426
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000427 instructions := instructionsFromExcludePathList(exclude)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000428 go func() {
429 context.wg.Add(1)
430 plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
431 context.wg.Wait()
432 close(context.depCh)
433 }()
434
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000435 for dep := range context.depCh {
436 deps = append(deps, dep)
437 }
438
Usta Shresthada15c612022-11-08 14:12:36 -0500439 return deps, context.mkdirCount.Load(), context.symlinkCount.Load()
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200440}