blob: 5c333085d23a5b39dcb77cdfba89028d7b2eadfc [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"
Cole Faust5db92092023-04-05 11:36:23 -070024 "strconv"
25 "strings"
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +000026 "sync"
27 "sync/atomic"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020028
29 "android/soong/shared"
Cole Faust3f4f5212023-04-04 15:12:22 -070030
Chris Parsons1a12d032023-02-06 22:37:41 -050031 "github.com/google/blueprint/pathtools"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020032)
33
34// A tree structure that describes what to do at each directory in the created
35// symlink tree. Currently it is used to enumerate which files/directories
36// should be excluded from symlinking. Each instance of "node" represents a file
37// or a directory. If excluded is true, then that file/directory should be
38// excluded from symlinking. Otherwise, the node is not excluded, but one of its
39// descendants is (otherwise the node in question would not exist)
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000040
Cole Faust5db92092023-04-05 11:36:23 -070041// This is a version int written to a file called symlink_forest_version at the root of the
42// symlink forest. If the version here does not match the version in the file, then we'll
43// clean the whole symlink forest and recreate it. This number can be bumped whenever there's
44// an incompatible change to the forest layout or a bug in incrementality that needs to be fixed
45// on machines that may still have the bug present in their forest.
46const symlinkForestVersion = 1
47
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000048type instructionsNode struct {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020049 name string
50 excluded bool // If false, this is just an intermediate node
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000051 children map[string]*instructionsNode
52}
53
54type symlinkForestContext struct {
55 verbose bool
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +000056 topdir string // $TOPDIR
57
58 // State
Usta Shresthada15c612022-11-08 14:12:36 -050059 wg sync.WaitGroup
60 depCh chan string
61 mkdirCount atomic.Uint64
62 symlinkCount atomic.Uint64
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020063}
64
Usta Shresthadb46a9b2022-07-11 11:29:56 -040065// Ensures that the node for the given path exists in the tree and returns it.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000066func ensureNodeExists(root *instructionsNode, path string) *instructionsNode {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020067 if path == "" {
68 return root
69 }
70
71 if path[len(path)-1] == '/' {
72 path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
73 }
74
75 dir, base := filepath.Split(path)
76
77 // First compute the parent node...
78 dn := ensureNodeExists(root, dir)
79
80 // then create the requested node as its direct child, if needed.
81 if child, ok := dn.children[base]; ok {
82 return child
83 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000084 dn.children[base] = &instructionsNode{base, false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020085 return dn.children[base]
86 }
87}
88
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000089// Turns a list of paths to be excluded into a tree
90func instructionsFromExcludePathList(paths []string) *instructionsNode {
91 result := &instructionsNode{"", false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020092
93 for _, p := range paths {
94 ensureNodeExists(result, p).excluded = true
95 }
96
97 return result
98}
99
Cole Faust324a92e2022-08-23 15:29:05 -0700100func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error {
101
102 srcBuildFileContent, err := os.ReadFile(srcBuildFile)
103 if err != nil {
104 return err
105 }
106
107 generatedBuildFileContent, err := os.ReadFile(generatedBuildFile)
108 if err != nil {
109 return err
110 }
111
112 // There can't be a package() call in both the source and generated BUILD files.
113 // bp2build will generate a package() call for licensing information, but if
114 // there's no licensing information, it will still generate a package() call
115 // that just sets default_visibility=public. If the handcrafted build file
116 // also has a package() call, we'll allow it to override the bp2build
117 // generated one if it doesn't have any licensing information. If the bp2build
118 // one has licensing information and the handcrafted one exists, we'll leave
119 // them both in for bazel to throw an error.
120 packageRegex := regexp.MustCompile(`(?m)^package\s*\(`)
121 packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`)
122 if packageRegex.Find(srcBuildFileContent) != nil {
123 if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil {
124 fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n",
125 generatedBuildFile, srcBuildFile)
126 }
127 generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
128 }
129
Chris Parsons1a12d032023-02-06 22:37:41 -0500130 newContents := generatedBuildFileContent
131 if newContents[len(newContents)-1] != '\n' {
132 newContents = append(newContents, '\n')
Cole Faust324a92e2022-08-23 15:29:05 -0700133 }
Chris Parsons1a12d032023-02-06 22:37:41 -0500134 newContents = append(newContents, srcBuildFileContent...)
Cole Faust324a92e2022-08-23 15:29:05 -0700135
Cole Faust3f4f5212023-04-04 15:12:22 -0700136 // Say you run bp2build 4 times:
137 // - The first time there's only an Android.bp file. bp2build will convert it to a build file
138 // under out/soong/bp2build, then symlink from the forest to that generated file
139 // - Then you add a handcrafted BUILD file in the same directory. bp2build will merge this with
140 // the generated one, and write the result to the output file in the forest. But the output
141 // file was a symlink to out/soong/bp2build from the previous step! So we erroneously update
142 // the file in out/soong/bp2build instead. So far this doesn't cause any problems...
143 // - You run a 3rd bp2build with no relevant changes. Everything continues to work.
144 // - You then add a comment to the handcrafted BUILD file. This causes a merge with the
145 // generated file again. But since we wrote to the generated file in step 2, the generated
146 // file has an old copy of the handcrafted file in it! This probably causes duplicate bazel
147 // targets.
148 // To solve this, if we see that the output file is a symlink from a previous build, remove it.
149 stat, err := os.Lstat(output)
150 if err != nil && !os.IsNotExist(err) {
151 return err
152 } else if err == nil {
153 if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
154 if verbose {
155 fmt.Fprintf(os.Stderr, "Removing symlink so that we can replace it with a merged file: %s\n", output)
156 }
157 err = os.Remove(output)
158 if err != nil {
159 return err
160 }
161 }
162 }
163
Chris Parsons1a12d032023-02-06 22:37:41 -0500164 return pathtools.WriteFileIfChanged(output, newContents, 0666)
Cole Faust324a92e2022-08-23 15:29:05 -0700165}
166
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200167// Calls readdir() and returns it as a map from the basename of the files in dir
168// to os.FileInfo.
169func readdirToMap(dir string) map[string]os.FileInfo {
170 entryList, err := ioutil.ReadDir(dir)
171 result := make(map[string]os.FileInfo)
172
173 if err != nil {
174 if os.IsNotExist(err) {
175 // It's okay if a directory doesn't exist; it just means that one of the
176 // trees to be merged contains parts the other doesn't
177 return result
178 } else {
179 fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
180 os.Exit(1)
181 }
182 }
183
184 for _, fi := range entryList {
185 result[fi.Name()] = fi
186 }
187
188 return result
189}
190
191// Creates a symbolic link at dst pointing to src
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500192func symlinkIntoForest(topdir, dst, src string) uint64 {
193 srcPath := shared.JoinPath(topdir, src)
194 dstPath := shared.JoinPath(topdir, dst)
195
196 // Check if a symlink already exists.
197 if dstInfo, err := os.Lstat(dstPath); err != nil {
198 if !os.IsNotExist(err) {
199 fmt.Fprintf(os.Stderr, "Failed to lstat '%s': %s", dst, err)
200 os.Exit(1)
201 }
202 } else {
203 if dstInfo.Mode()&os.ModeSymlink != 0 {
204 // Assume that the link's target is correct, i.e. no manual tampering.
205 // E.g. OUT_DIR could have been previously used with a different source tree check-out!
206 return 0
207 } else {
208 if err := os.RemoveAll(dstPath); err != nil {
209 fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", dst, err)
210 os.Exit(1)
211 }
212 }
213 }
214
215 // Create symlink.
216 if err := os.Symlink(srcPath, dstPath); err != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200217 fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
218 os.Exit(1)
219 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500220 return 1
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200221}
222
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200223func isDir(path string, fi os.FileInfo) bool {
224 if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
225 return fi.IsDir()
226 }
227
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000228 fi2, statErr := os.Stat(path)
229 if statErr == nil {
230 return fi2.IsDir()
231 }
232
233 // Check if this is a dangling symlink. If so, treat it like a file, not a dir.
234 _, lstatErr := os.Lstat(path)
235 if lstatErr != nil {
236 fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200237 os.Exit(1)
238 }
239
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000240 return false
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200241}
242
Cole Faust5db92092023-04-05 11:36:23 -0700243// maybeCleanSymlinkForest will remove the whole symlink forest directory if the version recorded
244// in the symlink_forest_version file is not equal to symlinkForestVersion.
245func maybeCleanSymlinkForest(topdir, forest string, verbose bool) error {
246 versionFilePath := shared.JoinPath(topdir, forest, "symlink_forest_version")
247 versionFileContents, err := os.ReadFile(versionFilePath)
248 if err != nil && !os.IsNotExist(err) {
249 return err
250 }
251 versionFileString := strings.TrimSpace(string(versionFileContents))
252 symlinkForestVersionString := strconv.Itoa(symlinkForestVersion)
253 if err != nil || versionFileString != symlinkForestVersionString {
254 if verbose {
255 fmt.Fprintf(os.Stderr, "Old symlink_forest_version was %q, current is %q. Cleaning symlink forest before recreating...\n", versionFileString, symlinkForestVersionString)
256 }
257 err = os.RemoveAll(shared.JoinPath(topdir, forest))
258 if err != nil {
259 return err
260 }
261 }
262 return nil
263}
264
265// maybeWriteVersionFile will write the symlink_forest_version file containing symlinkForestVersion
266// if it doesn't exist already. If it exists we know it must contain symlinkForestVersion because
267// we checked for that already in maybeCleanSymlinkForest
268func maybeWriteVersionFile(topdir, forest string) error {
269 versionFilePath := shared.JoinPath(topdir, forest, "symlink_forest_version")
270 _, err := os.Stat(versionFilePath)
271 if err != nil {
272 if !os.IsNotExist(err) {
273 return err
274 }
275 err = os.WriteFile(versionFilePath, []byte(strconv.Itoa(symlinkForestVersion)+"\n"), 0666)
276 if err != nil {
277 return err
278 }
279 }
280 return nil
281}
282
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200283// Recursively plants a symlink forest at forestDir. The symlink tree will
284// contain every file in buildFilesDir and srcDir excluding the files in
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000285// instructions. Collects every directory encountered during the traversal of
286// srcDir .
287func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *instructionsNode, forestDir string, buildFilesDir string, srcDir string) {
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000288 defer context.wg.Done()
289
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000290 if instructions != nil && instructions.excluded {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500291 // Excluded paths are skipped at the level of the non-excluded parent.
292 fmt.Fprintf(os.Stderr, "may not specify a root-level exclude directory '%s'", srcDir)
293 os.Exit(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200294 }
295
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000296 // We don't add buildFilesDir here because the bp2build files marker files is
297 // already a dependency which covers it. If we ever wanted to turn this into
298 // a generic symlink forest creation tool, we'd need to add it, too.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000299 context.depCh <- srcDir
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000300
301 srcDirMap := readdirToMap(shared.JoinPath(context.topdir, srcDir))
302 buildFilesMap := readdirToMap(shared.JoinPath(context.topdir, buildFilesDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200303
Cole Faust324a92e2022-08-23 15:29:05 -0700304 renamingBuildFile := false
305 if _, ok := srcDirMap["BUILD"]; ok {
306 if _, ok := srcDirMap["BUILD.bazel"]; !ok {
307 if _, ok := buildFilesMap["BUILD.bazel"]; ok {
308 renamingBuildFile = true
309 srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
310 delete(srcDirMap, "BUILD")
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500311 if instructions != nil {
312 if _, ok := instructions.children["BUILD"]; ok {
313 instructions.children["BUILD.bazel"] = instructions.children["BUILD"]
314 delete(instructions.children, "BUILD")
315 }
316 }
Cole Faust324a92e2022-08-23 15:29:05 -0700317 }
318 }
319 }
320
Cole Faust358ba4f2023-01-18 15:00:42 -0800321 allEntries := make([]string, 0, len(srcDirMap)+len(buildFilesMap))
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400322 for n := range srcDirMap {
Cole Faust358ba4f2023-01-18 15:00:42 -0800323 allEntries = append(allEntries, n)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200324 }
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400325 for n := range buildFilesMap {
Cole Faust358ba4f2023-01-18 15:00:42 -0800326 if _, ok := srcDirMap[n]; !ok {
327 allEntries = append(allEntries, n)
328 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200329 }
Cole Faust358ba4f2023-01-18 15:00:42 -0800330 // Tests read the error messages generated, so ensure their order is deterministic
331 sort.Strings(allEntries)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200332
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500333 fullForestPath := shared.JoinPath(context.topdir, forestDir)
334 createForestDir := false
335 if fi, err := os.Lstat(fullForestPath); err != nil {
336 if os.IsNotExist(err) {
337 createForestDir = true
338 } else {
339 fmt.Fprintf(os.Stderr, "Could not read info for '%s': %s\n", forestDir, err)
340 }
341 } else if fi.Mode()&os.ModeDir == 0 {
342 if err := os.RemoveAll(fullForestPath); err != nil {
343 fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", forestDir, err)
344 os.Exit(1)
345 }
346 createForestDir = true
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200347 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500348 if createForestDir {
349 if err := os.MkdirAll(fullForestPath, 0777); err != nil {
350 fmt.Fprintf(os.Stderr, "Could not mkdir '%s': %s\n", forestDir, err)
351 os.Exit(1)
352 }
353 context.mkdirCount.Add(1)
354 }
355
356 // Start with a list of items that already exist in the forest, and remove
357 // each element as it is processed in allEntries. Any remaining items in
358 // forestMapForDeletion must be removed. (This handles files which were
359 // removed since the previous forest generation).
360 forestMapForDeletion := readdirToMap(shared.JoinPath(context.topdir, forestDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200361
Cole Faust358ba4f2023-01-18 15:00:42 -0800362 for _, f := range allEntries {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200363 if f[0] == '.' {
364 continue // Ignore dotfiles
365 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500366 delete(forestMapForDeletion, f)
367 // todo add deletionCount metric
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200368
369 // The full paths of children in the input trees and in the output tree
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200370 forestChild := shared.JoinPath(forestDir, f)
371 srcChild := shared.JoinPath(srcDir, f)
Cole Faust324a92e2022-08-23 15:29:05 -0700372 if f == "BUILD.bazel" && renamingBuildFile {
373 srcChild = shared.JoinPath(srcDir, "BUILD")
374 }
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200375 buildFilesChild := shared.JoinPath(buildFilesDir, f)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200376
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000377 // Descend in the instruction tree if it exists
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500378 var instructionsChild *instructionsNode
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000379 if instructions != nil {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500380 instructionsChild = instructions.children[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200381 }
382
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200383 srcChildEntry, sExists := srcDirMap[f]
384 buildFilesChildEntry, bExists := buildFilesMap[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200385
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000386 if instructionsChild != nil && instructionsChild.excluded {
Cole Faust324a92e2022-08-23 15:29:05 -0700387 if bExists {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500388 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
Cole Faust324a92e2022-08-23 15:29:05 -0700389 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200390 continue
391 }
392
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000393 sDir := sExists && isDir(shared.JoinPath(context.topdir, srcChild), srcChildEntry)
394 bDir := bExists && isDir(shared.JoinPath(context.topdir, buildFilesChild), buildFilesChildEntry)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200395
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200396 if !sExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000397 if bDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200398 // Not in the source tree, but we have to exclude something from under
399 // this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000400 context.wg.Add(1)
401 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200402 } else {
403 // Not in the source tree, symlink BUILD file
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500404 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200405 }
406 } else if !bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000407 if sDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200408 // Not in the build file tree, but we have to exclude something from
409 // under this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000410 context.wg.Add(1)
411 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200412 } else {
413 // Not in the build file tree, symlink source tree, carry on
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500414 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, srcChild))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200415 }
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200416 } else if sDir && bDir {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200417 // Both are directories. Descend.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000418 context.wg.Add(1)
419 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200420 } else if !sDir && !bDir {
Cole Faust324a92e2022-08-23 15:29:05 -0700421 // Neither is a directory. Merge them.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000422 srcBuildFile := shared.JoinPath(context.topdir, srcChild)
423 generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild)
Usta Shrestha783ec5c2022-10-25 01:22:36 -0400424 // The Android.bp file that codegen used to produce `buildFilesChild` is
425 // already a dependency, we can ignore `buildFilesChild`.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000426 context.depCh <- srcChild
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500427 if err := mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose); err != nil {
Cole Faust324a92e2022-08-23 15:29:05 -0700428 fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
429 srcBuildFile, generatedBuildFile, err)
Usta Shrestha071f6c22022-10-25 12:34:06 -0400430 os.Exit(1)
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700431 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200432 } else {
433 // Both exist and one is a file. This is an error.
434 fmt.Fprintf(os.Stderr,
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200435 "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 +0200436 srcChild, buildFilesChild)
Usta Shrestha071f6c22022-10-25 12:34:06 -0400437 os.Exit(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200438 }
439 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200440
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500441 // Remove all files in the forest that exist in neither the source
442 // tree nor the build files tree. (This handles files which were removed
443 // since the previous forest generation).
444 for f := range forestMapForDeletion {
445 var instructionsChild *instructionsNode
446 if instructions != nil {
447 instructionsChild = instructions.children[f]
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000448 }
449
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500450 if instructionsChild != nil && instructionsChild.excluded {
451 // This directory may be excluded because bazel writes to it under the
452 // forest root. Thus this path is intentionally left alone.
453 continue
454 }
455 forestChild := shared.JoinPath(context.topdir, forestDir, f)
456 if err := os.RemoveAll(forestChild); err != nil {
457 fmt.Fprintf(os.Stderr, "Failed to remove '%s/%s': %s", forestDir, f, err)
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000458 os.Exit(1)
459 }
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000460 }
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000461}
462
Usta Shresthada15c612022-11-08 14:12:36 -0500463// PlantSymlinkForest Creates a symlink forest by merging the directory tree at "buildFiles" and
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200464// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
465// under srcDir on which readdir() had to be called to produce the symlink
466// forest.
Usta Shresthada15c612022-11-08 14:12:36 -0500467func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) (deps []string, mkdirCount, symlinkCount uint64) {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000468 context := &symlinkForestContext{
Usta Shresthada15c612022-11-08 14:12:36 -0500469 verbose: verbose,
470 topdir: topdir,
471 depCh: make(chan string),
472 mkdirCount: atomic.Uint64{},
473 symlinkCount: atomic.Uint64{},
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000474 }
475
Cole Faust5db92092023-04-05 11:36:23 -0700476 err := maybeCleanSymlinkForest(topdir, forest, verbose)
477 if err != nil {
478 fmt.Fprintln(os.Stderr, err)
479 os.Exit(1)
480 }
481
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000482 instructions := instructionsFromExcludePathList(exclude)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000483 go func() {
484 context.wg.Add(1)
485 plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
486 context.wg.Wait()
487 close(context.depCh)
488 }()
489
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000490 for dep := range context.depCh {
491 deps = append(deps, dep)
492 }
493
Cole Faust5db92092023-04-05 11:36:23 -0700494 err = maybeWriteVersionFile(topdir, forest)
495 if err != nil {
496 fmt.Fprintln(os.Stderr, err)
497 os.Exit(1)
498 }
499
Usta Shresthada15c612022-11-08 14:12:36 -0500500 return deps, context.mkdirCount.Load(), context.symlinkCount.Load()
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200501}