blob: aac5e7d2cbea22d8427fce9b2726e30ae3b2de9b [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"
Chris Parsons1a12d032023-02-06 22:37:41 -050028 "github.com/google/blueprint/pathtools"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020029)
30
31// A tree structure that describes what to do at each directory in the created
32// symlink tree. Currently it is used to enumerate which files/directories
33// should be excluded from symlinking. Each instance of "node" represents a file
34// or a directory. If excluded is true, then that file/directory should be
35// excluded from symlinking. Otherwise, the node is not excluded, but one of its
36// descendants is (otherwise the node in question would not exist)
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000037
38type instructionsNode struct {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020039 name string
40 excluded bool // If false, this is just an intermediate node
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000041 children map[string]*instructionsNode
42}
43
44type symlinkForestContext struct {
45 verbose bool
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +000046 topdir string // $TOPDIR
47
48 // State
Usta Shresthada15c612022-11-08 14:12:36 -050049 wg sync.WaitGroup
50 depCh chan string
51 mkdirCount atomic.Uint64
52 symlinkCount atomic.Uint64
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020053}
54
Usta Shresthadb46a9b2022-07-11 11:29:56 -040055// Ensures that the node for the given path exists in the tree and returns it.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000056func ensureNodeExists(root *instructionsNode, path string) *instructionsNode {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020057 if path == "" {
58 return root
59 }
60
61 if path[len(path)-1] == '/' {
62 path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
63 }
64
65 dir, base := filepath.Split(path)
66
67 // First compute the parent node...
68 dn := ensureNodeExists(root, dir)
69
70 // then create the requested node as its direct child, if needed.
71 if child, ok := dn.children[base]; ok {
72 return child
73 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000074 dn.children[base] = &instructionsNode{base, false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020075 return dn.children[base]
76 }
77}
78
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000079// Turns a list of paths to be excluded into a tree
80func instructionsFromExcludePathList(paths []string) *instructionsNode {
81 result := &instructionsNode{"", false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020082
83 for _, p := range paths {
84 ensureNodeExists(result, p).excluded = true
85 }
86
87 return result
88}
89
Cole Faust324a92e2022-08-23 15:29:05 -070090func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error {
91
92 srcBuildFileContent, err := os.ReadFile(srcBuildFile)
93 if err != nil {
94 return err
95 }
96
97 generatedBuildFileContent, err := os.ReadFile(generatedBuildFile)
98 if err != nil {
99 return err
100 }
101
102 // There can't be a package() call in both the source and generated BUILD files.
103 // bp2build will generate a package() call for licensing information, but if
104 // there's no licensing information, it will still generate a package() call
105 // that just sets default_visibility=public. If the handcrafted build file
106 // also has a package() call, we'll allow it to override the bp2build
107 // generated one if it doesn't have any licensing information. If the bp2build
108 // one has licensing information and the handcrafted one exists, we'll leave
109 // them both in for bazel to throw an error.
110 packageRegex := regexp.MustCompile(`(?m)^package\s*\(`)
111 packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`)
112 if packageRegex.Find(srcBuildFileContent) != nil {
113 if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil {
114 fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n",
115 generatedBuildFile, srcBuildFile)
116 }
117 generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
118 }
119
Chris Parsons1a12d032023-02-06 22:37:41 -0500120 newContents := generatedBuildFileContent
121 if newContents[len(newContents)-1] != '\n' {
122 newContents = append(newContents, '\n')
Cole Faust324a92e2022-08-23 15:29:05 -0700123 }
Chris Parsons1a12d032023-02-06 22:37:41 -0500124 newContents = append(newContents, srcBuildFileContent...)
Cole Faust324a92e2022-08-23 15:29:05 -0700125
Chris Parsons1a12d032023-02-06 22:37:41 -0500126 return pathtools.WriteFileIfChanged(output, newContents, 0666)
Cole Faust324a92e2022-08-23 15:29:05 -0700127}
128
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200129// Calls readdir() and returns it as a map from the basename of the files in dir
130// to os.FileInfo.
131func readdirToMap(dir string) map[string]os.FileInfo {
132 entryList, err := ioutil.ReadDir(dir)
133 result := make(map[string]os.FileInfo)
134
135 if err != nil {
136 if os.IsNotExist(err) {
137 // It's okay if a directory doesn't exist; it just means that one of the
138 // trees to be merged contains parts the other doesn't
139 return result
140 } else {
141 fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
142 os.Exit(1)
143 }
144 }
145
146 for _, fi := range entryList {
147 result[fi.Name()] = fi
148 }
149
150 return result
151}
152
153// Creates a symbolic link at dst pointing to src
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500154func symlinkIntoForest(topdir, dst, src string) uint64 {
155 srcPath := shared.JoinPath(topdir, src)
156 dstPath := shared.JoinPath(topdir, dst)
157
158 // Check if a symlink already exists.
159 if dstInfo, err := os.Lstat(dstPath); err != nil {
160 if !os.IsNotExist(err) {
161 fmt.Fprintf(os.Stderr, "Failed to lstat '%s': %s", dst, err)
162 os.Exit(1)
163 }
164 } else {
165 if dstInfo.Mode()&os.ModeSymlink != 0 {
166 // Assume that the link's target is correct, i.e. no manual tampering.
167 // E.g. OUT_DIR could have been previously used with a different source tree check-out!
168 return 0
169 } else {
170 if err := os.RemoveAll(dstPath); err != nil {
171 fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", dst, err)
172 os.Exit(1)
173 }
174 }
175 }
176
177 // Create symlink.
178 if err := os.Symlink(srcPath, dstPath); err != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200179 fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
180 os.Exit(1)
181 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500182 return 1
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200183}
184
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200185func isDir(path string, fi os.FileInfo) bool {
186 if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
187 return fi.IsDir()
188 }
189
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000190 fi2, statErr := os.Stat(path)
191 if statErr == nil {
192 return fi2.IsDir()
193 }
194
195 // Check if this is a dangling symlink. If so, treat it like a file, not a dir.
196 _, lstatErr := os.Lstat(path)
197 if lstatErr != nil {
198 fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200199 os.Exit(1)
200 }
201
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000202 return false
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200203}
204
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200205// Recursively plants a symlink forest at forestDir. The symlink tree will
206// contain every file in buildFilesDir and srcDir excluding the files in
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000207// instructions. Collects every directory encountered during the traversal of
208// srcDir .
209func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *instructionsNode, forestDir string, buildFilesDir string, srcDir string) {
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000210 defer context.wg.Done()
211
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000212 if instructions != nil && instructions.excluded {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500213 // Excluded paths are skipped at the level of the non-excluded parent.
214 fmt.Fprintf(os.Stderr, "may not specify a root-level exclude directory '%s'", srcDir)
215 os.Exit(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200216 }
217
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000218 // We don't add buildFilesDir here because the bp2build files marker files is
219 // already a dependency which covers it. If we ever wanted to turn this into
220 // a generic symlink forest creation tool, we'd need to add it, too.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000221 context.depCh <- srcDir
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000222
223 srcDirMap := readdirToMap(shared.JoinPath(context.topdir, srcDir))
224 buildFilesMap := readdirToMap(shared.JoinPath(context.topdir, buildFilesDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200225
Cole Faust324a92e2022-08-23 15:29:05 -0700226 renamingBuildFile := false
227 if _, ok := srcDirMap["BUILD"]; ok {
228 if _, ok := srcDirMap["BUILD.bazel"]; !ok {
229 if _, ok := buildFilesMap["BUILD.bazel"]; ok {
230 renamingBuildFile = true
231 srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
232 delete(srcDirMap, "BUILD")
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500233 if instructions != nil {
234 if _, ok := instructions.children["BUILD"]; ok {
235 instructions.children["BUILD.bazel"] = instructions.children["BUILD"]
236 delete(instructions.children, "BUILD")
237 }
238 }
Cole Faust324a92e2022-08-23 15:29:05 -0700239 }
240 }
241 }
242
Cole Faust358ba4f2023-01-18 15:00:42 -0800243 allEntries := make([]string, 0, len(srcDirMap)+len(buildFilesMap))
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400244 for n := range srcDirMap {
Cole Faust358ba4f2023-01-18 15:00:42 -0800245 allEntries = append(allEntries, n)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200246 }
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400247 for n := range buildFilesMap {
Cole Faust358ba4f2023-01-18 15:00:42 -0800248 if _, ok := srcDirMap[n]; !ok {
249 allEntries = append(allEntries, n)
250 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200251 }
Cole Faust358ba4f2023-01-18 15:00:42 -0800252 // Tests read the error messages generated, so ensure their order is deterministic
253 sort.Strings(allEntries)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200254
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500255 fullForestPath := shared.JoinPath(context.topdir, forestDir)
256 createForestDir := false
257 if fi, err := os.Lstat(fullForestPath); err != nil {
258 if os.IsNotExist(err) {
259 createForestDir = true
260 } else {
261 fmt.Fprintf(os.Stderr, "Could not read info for '%s': %s\n", forestDir, err)
262 }
263 } else if fi.Mode()&os.ModeDir == 0 {
264 if err := os.RemoveAll(fullForestPath); err != nil {
265 fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", forestDir, err)
266 os.Exit(1)
267 }
268 createForestDir = true
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200269 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500270 if createForestDir {
271 if err := os.MkdirAll(fullForestPath, 0777); err != nil {
272 fmt.Fprintf(os.Stderr, "Could not mkdir '%s': %s\n", forestDir, err)
273 os.Exit(1)
274 }
275 context.mkdirCount.Add(1)
276 }
277
278 // Start with a list of items that already exist in the forest, and remove
279 // each element as it is processed in allEntries. Any remaining items in
280 // forestMapForDeletion must be removed. (This handles files which were
281 // removed since the previous forest generation).
282 forestMapForDeletion := readdirToMap(shared.JoinPath(context.topdir, forestDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200283
Cole Faust358ba4f2023-01-18 15:00:42 -0800284 for _, f := range allEntries {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200285 if f[0] == '.' {
286 continue // Ignore dotfiles
287 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500288 delete(forestMapForDeletion, f)
289 // todo add deletionCount metric
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200290
291 // The full paths of children in the input trees and in the output tree
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200292 forestChild := shared.JoinPath(forestDir, f)
293 srcChild := shared.JoinPath(srcDir, f)
Cole Faust324a92e2022-08-23 15:29:05 -0700294 if f == "BUILD.bazel" && renamingBuildFile {
295 srcChild = shared.JoinPath(srcDir, "BUILD")
296 }
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200297 buildFilesChild := shared.JoinPath(buildFilesDir, f)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200298
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000299 // Descend in the instruction tree if it exists
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500300 var instructionsChild *instructionsNode
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000301 if instructions != nil {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500302 instructionsChild = instructions.children[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200303 }
304
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200305 srcChildEntry, sExists := srcDirMap[f]
306 buildFilesChildEntry, bExists := buildFilesMap[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200307
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000308 if instructionsChild != nil && instructionsChild.excluded {
Cole Faust324a92e2022-08-23 15:29:05 -0700309 if bExists {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500310 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
Cole Faust324a92e2022-08-23 15:29:05 -0700311 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200312 continue
313 }
314
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000315 sDir := sExists && isDir(shared.JoinPath(context.topdir, srcChild), srcChildEntry)
316 bDir := bExists && isDir(shared.JoinPath(context.topdir, buildFilesChild), buildFilesChildEntry)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200317
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200318 if !sExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000319 if bDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200320 // Not in the source tree, but we have to exclude something from under
321 // this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000322 context.wg.Add(1)
323 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200324 } else {
325 // Not in the source tree, symlink BUILD file
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500326 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200327 }
328 } else if !bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000329 if sDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200330 // Not in the build file tree, but we have to exclude something from
331 // under this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000332 context.wg.Add(1)
333 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200334 } else {
335 // Not in the build file tree, symlink source tree, carry on
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500336 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, srcChild))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200337 }
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200338 } else if sDir && bDir {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200339 // Both are directories. Descend.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000340 context.wg.Add(1)
341 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200342 } else if !sDir && !bDir {
Cole Faust324a92e2022-08-23 15:29:05 -0700343 // Neither is a directory. Merge them.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000344 srcBuildFile := shared.JoinPath(context.topdir, srcChild)
345 generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild)
Usta Shrestha783ec5c2022-10-25 01:22:36 -0400346 // The Android.bp file that codegen used to produce `buildFilesChild` is
347 // already a dependency, we can ignore `buildFilesChild`.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000348 context.depCh <- srcChild
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500349 if err := mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose); err != nil {
Cole Faust324a92e2022-08-23 15:29:05 -0700350 fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
351 srcBuildFile, generatedBuildFile, err)
Usta Shrestha071f6c22022-10-25 12:34:06 -0400352 os.Exit(1)
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700353 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200354 } else {
355 // Both exist and one is a file. This is an error.
356 fmt.Fprintf(os.Stderr,
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200357 "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 +0200358 srcChild, buildFilesChild)
Usta Shrestha071f6c22022-10-25 12:34:06 -0400359 os.Exit(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200360 }
361 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200362
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500363 // Remove all files in the forest that exist in neither the source
364 // tree nor the build files tree. (This handles files which were removed
365 // since the previous forest generation).
366 for f := range forestMapForDeletion {
367 var instructionsChild *instructionsNode
368 if instructions != nil {
369 instructionsChild = instructions.children[f]
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000370 }
371
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500372 if instructionsChild != nil && instructionsChild.excluded {
373 // This directory may be excluded because bazel writes to it under the
374 // forest root. Thus this path is intentionally left alone.
375 continue
376 }
377 forestChild := shared.JoinPath(context.topdir, forestDir, f)
378 if err := os.RemoveAll(forestChild); err != nil {
379 fmt.Fprintf(os.Stderr, "Failed to remove '%s/%s': %s", forestDir, f, err)
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000380 os.Exit(1)
381 }
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000382 }
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000383}
384
Usta Shresthada15c612022-11-08 14:12:36 -0500385// PlantSymlinkForest Creates a symlink forest by merging the directory tree at "buildFiles" and
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200386// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
387// under srcDir on which readdir() had to be called to produce the symlink
388// forest.
Usta Shresthada15c612022-11-08 14:12:36 -0500389func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) (deps []string, mkdirCount, symlinkCount uint64) {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000390 context := &symlinkForestContext{
Usta Shresthada15c612022-11-08 14:12:36 -0500391 verbose: verbose,
392 topdir: topdir,
393 depCh: make(chan string),
394 mkdirCount: atomic.Uint64{},
395 symlinkCount: atomic.Uint64{},
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000396 }
397
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000398 instructions := instructionsFromExcludePathList(exclude)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000399 go func() {
400 context.wg.Add(1)
401 plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
402 context.wg.Wait()
403 close(context.depCh)
404 }()
405
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000406 for dep := range context.depCh {
407 deps = append(deps, dep)
408 }
409
Usta Shresthada15c612022-11-08 14:12:36 -0500410 return deps, context.mkdirCount.Load(), context.symlinkCount.Load()
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200411}