blob: 15a6df03afb99e1c17ef00836b1ef5390394ff4e [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"
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +000025 "sync"
26 "sync/atomic"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020027
28 "android/soong/shared"
Cole Faust3f4f5212023-04-04 15:12:22 -070029
Chris Parsons1a12d032023-02-06 22:37:41 -050030 "github.com/google/blueprint/pathtools"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020031)
32
33// A tree structure that describes what to do at each directory in the created
Mark Dacekb2441c52023-10-04 16:12:00 +000034// symlink tree. Currently, it is used to enumerate which files/directories
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020035// should be excluded from symlinking. Each instance of "node" represents a file
36// or a directory. If excluded is true, then that file/directory should be
37// excluded from symlinking. Otherwise, the node is not excluded, but one of its
38// descendants is (otherwise the node in question would not exist)
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000039
40type instructionsNode struct {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020041 name string
42 excluded bool // If false, this is just an intermediate node
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000043 children map[string]*instructionsNode
44}
45
46type symlinkForestContext struct {
47 verbose bool
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +000048 topdir string // $TOPDIR
49
50 // State
Usta Shresthada15c612022-11-08 14:12:36 -050051 wg sync.WaitGroup
52 depCh chan string
53 mkdirCount atomic.Uint64
54 symlinkCount atomic.Uint64
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020055}
56
Usta Shresthadb46a9b2022-07-11 11:29:56 -040057// Ensures that the node for the given path exists in the tree and returns it.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000058func ensureNodeExists(root *instructionsNode, path string) *instructionsNode {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020059 if path == "" {
60 return root
61 }
62
63 if path[len(path)-1] == '/' {
64 path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
65 }
66
67 dir, base := filepath.Split(path)
68
69 // First compute the parent node...
70 dn := ensureNodeExists(root, dir)
71
72 // then create the requested node as its direct child, if needed.
73 if child, ok := dn.children[base]; ok {
74 return child
75 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000076 dn.children[base] = &instructionsNode{base, false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020077 return dn.children[base]
78 }
79}
80
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000081// Turns a list of paths to be excluded into a tree
82func instructionsFromExcludePathList(paths []string) *instructionsNode {
83 result := &instructionsNode{"", false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020084
85 for _, p := range paths {
86 ensureNodeExists(result, p).excluded = true
87 }
88
89 return result
90}
91
Cole Faust324a92e2022-08-23 15:29:05 -070092func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error {
93
94 srcBuildFileContent, err := os.ReadFile(srcBuildFile)
95 if err != nil {
96 return err
97 }
98
99 generatedBuildFileContent, err := os.ReadFile(generatedBuildFile)
100 if err != nil {
101 return err
102 }
103
104 // There can't be a package() call in both the source and generated BUILD files.
105 // bp2build will generate a package() call for licensing information, but if
106 // there's no licensing information, it will still generate a package() call
107 // that just sets default_visibility=public. If the handcrafted build file
108 // also has a package() call, we'll allow it to override the bp2build
109 // generated one if it doesn't have any licensing information. If the bp2build
110 // one has licensing information and the handcrafted one exists, we'll leave
111 // them both in for bazel to throw an error.
112 packageRegex := regexp.MustCompile(`(?m)^package\s*\(`)
113 packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`)
114 if packageRegex.Find(srcBuildFileContent) != nil {
115 if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil {
116 fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n",
117 generatedBuildFile, srcBuildFile)
118 }
119 generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
120 }
121
Chris Parsons1a12d032023-02-06 22:37:41 -0500122 newContents := generatedBuildFileContent
123 if newContents[len(newContents)-1] != '\n' {
124 newContents = append(newContents, '\n')
Cole Faust324a92e2022-08-23 15:29:05 -0700125 }
Chris Parsons1a12d032023-02-06 22:37:41 -0500126 newContents = append(newContents, srcBuildFileContent...)
Cole Faust324a92e2022-08-23 15:29:05 -0700127
Cole Faust3f4f5212023-04-04 15:12:22 -0700128 // Say you run bp2build 4 times:
129 // - The first time there's only an Android.bp file. bp2build will convert it to a build file
130 // under out/soong/bp2build, then symlink from the forest to that generated file
131 // - Then you add a handcrafted BUILD file in the same directory. bp2build will merge this with
132 // the generated one, and write the result to the output file in the forest. But the output
133 // file was a symlink to out/soong/bp2build from the previous step! So we erroneously update
134 // the file in out/soong/bp2build instead. So far this doesn't cause any problems...
135 // - You run a 3rd bp2build with no relevant changes. Everything continues to work.
136 // - You then add a comment to the handcrafted BUILD file. This causes a merge with the
137 // generated file again. But since we wrote to the generated file in step 2, the generated
138 // file has an old copy of the handcrafted file in it! This probably causes duplicate bazel
139 // targets.
140 // To solve this, if we see that the output file is a symlink from a previous build, remove it.
141 stat, err := os.Lstat(output)
142 if err != nil && !os.IsNotExist(err) {
143 return err
144 } else if err == nil {
145 if stat.Mode()&os.ModeSymlink == os.ModeSymlink {
146 if verbose {
147 fmt.Fprintf(os.Stderr, "Removing symlink so that we can replace it with a merged file: %s\n", output)
148 }
149 err = os.Remove(output)
150 if err != nil {
151 return err
152 }
153 }
154 }
155
Chris Parsons1a12d032023-02-06 22:37:41 -0500156 return pathtools.WriteFileIfChanged(output, newContents, 0666)
Cole Faust324a92e2022-08-23 15:29:05 -0700157}
158
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200159// Calls readdir() and returns it as a map from the basename of the files in dir
160// to os.FileInfo.
161func readdirToMap(dir string) map[string]os.FileInfo {
162 entryList, err := ioutil.ReadDir(dir)
163 result := make(map[string]os.FileInfo)
164
165 if err != nil {
166 if os.IsNotExist(err) {
167 // It's okay if a directory doesn't exist; it just means that one of the
168 // trees to be merged contains parts the other doesn't
169 return result
170 } else {
171 fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
172 os.Exit(1)
173 }
174 }
175
176 for _, fi := range entryList {
177 result[fi.Name()] = fi
178 }
179
180 return result
181}
182
183// Creates a symbolic link at dst pointing to src
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500184func symlinkIntoForest(topdir, dst, src string) uint64 {
Sebastian Pickl90355f72023-09-12 18:03:01 +0000185 srcPath := shared.JoinPath(topdir, src)
186 dstPath := shared.JoinPath(topdir, dst)
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500187
Mark Dacekb2441c52023-10-04 16:12:00 +0000188 // Check whether a symlink already exists.
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500189 if dstInfo, err := os.Lstat(dstPath); err != nil {
190 if !os.IsNotExist(err) {
191 fmt.Fprintf(os.Stderr, "Failed to lstat '%s': %s", dst, err)
192 os.Exit(1)
193 }
194 } else {
195 if dstInfo.Mode()&os.ModeSymlink != 0 {
196 // Assume that the link's target is correct, i.e. no manual tampering.
197 // E.g. OUT_DIR could have been previously used with a different source tree check-out!
198 return 0
199 } else {
200 if err := os.RemoveAll(dstPath); err != nil {
201 fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", dst, err)
202 os.Exit(1)
203 }
204 }
205 }
206
207 // Create symlink.
208 if err := os.Symlink(srcPath, dstPath); err != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200209 fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
210 os.Exit(1)
211 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500212 return 1
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200213}
214
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200215func isDir(path string, fi os.FileInfo) bool {
216 if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
217 return fi.IsDir()
218 }
219
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000220 fi2, statErr := os.Stat(path)
221 if statErr == nil {
222 return fi2.IsDir()
223 }
224
225 // Check if this is a dangling symlink. If so, treat it like a file, not a dir.
226 _, lstatErr := os.Lstat(path)
227 if lstatErr != nil {
228 fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200229 os.Exit(1)
230 }
231
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000232 return false
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200233}
234
Mark Dacekb2441c52023-10-04 16:12:00 +0000235// Returns the mtime of the soong_build binary to determine whether we should
236// force symlink_forest to re-execute
237func getSoongBuildMTime() (int64, error) {
238 binaryPath, err := os.Executable()
239 if err != nil {
240 return 0, err
Cole Faust5db92092023-04-05 11:36:23 -0700241 }
Mark Dacekb2441c52023-10-04 16:12:00 +0000242
243 info, err := os.Stat(binaryPath)
244 if err != nil {
245 return 0, err
Cole Faust5db92092023-04-05 11:36:23 -0700246 }
Mark Dacekb2441c52023-10-04 16:12:00 +0000247
248 return info.ModTime().UnixMilli(), nil
Cole Faust5db92092023-04-05 11:36:23 -0700249}
250
Mark Dacekb2441c52023-10-04 16:12:00 +0000251// cleanSymlinkForest will remove the whole symlink forest directory
252func cleanSymlinkForest(topdir, forest string) error {
253 return os.RemoveAll(shared.JoinPath(topdir, forest))
254}
255
256// This returns whether symlink forest should clean and replant symlinks.
257// It compares the mtime of this executable with the mtime of the last-run
258// soong_build binary. If they differ, then we should clean and replant.
259func shouldCleanSymlinkForest(topdir string, forest string, soongBuildMTime int64) (bool, error) {
260 mtimeFilePath := shared.JoinPath(topdir, forest, "soong_build_mtime")
261 mtimeFileContents, err := os.ReadFile(mtimeFilePath)
Mark Dacekaa5cc2c2023-10-02 23:40:33 +0000262 if err != nil {
Mark Dacekb2441c52023-10-04 16:12:00 +0000263 if os.IsNotExist(err) {
264 // This is likely the first time this has run with this functionality - clean away!
265 return true, nil
266 } else {
267 return false, err
Mark Dacekaa5cc2c2023-10-02 23:40:33 +0000268 }
269 }
Mark Dacekb2441c52023-10-04 16:12:00 +0000270 return strconv.FormatInt(soongBuildMTime, 10) != string(mtimeFileContents), nil
271}
272
273func writeSoongBuildMTimeFile(topdir, forest string, mtime int64) error {
274 mtimeFilePath := shared.JoinPath(topdir, forest, "soong_build_mtime")
275 contents := []byte(strconv.FormatInt(mtime, 10))
276
277 return os.WriteFile(mtimeFilePath, contents, 0666)
Cole Faust5db92092023-04-05 11:36:23 -0700278}
279
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200280// Recursively plants a symlink forest at forestDir. The symlink tree will
281// contain every file in buildFilesDir and srcDir excluding the files in
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000282// instructions. Collects every directory encountered during the traversal of
283// srcDir .
284func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *instructionsNode, forestDir string, buildFilesDir string, srcDir string) {
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000285 defer context.wg.Done()
286
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000287 if instructions != nil && instructions.excluded {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500288 // Excluded paths are skipped at the level of the non-excluded parent.
289 fmt.Fprintf(os.Stderr, "may not specify a root-level exclude directory '%s'", srcDir)
290 os.Exit(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200291 }
292
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000293 // We don't add buildFilesDir here because the bp2build files marker files is
294 // already a dependency which covers it. If we ever wanted to turn this into
295 // a generic symlink forest creation tool, we'd need to add it, too.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000296 context.depCh <- srcDir
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000297
298 srcDirMap := readdirToMap(shared.JoinPath(context.topdir, srcDir))
299 buildFilesMap := readdirToMap(shared.JoinPath(context.topdir, buildFilesDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200300
Cole Faust324a92e2022-08-23 15:29:05 -0700301 renamingBuildFile := false
302 if _, ok := srcDirMap["BUILD"]; ok {
303 if _, ok := srcDirMap["BUILD.bazel"]; !ok {
304 if _, ok := buildFilesMap["BUILD.bazel"]; ok {
305 renamingBuildFile = true
306 srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
307 delete(srcDirMap, "BUILD")
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500308 if instructions != nil {
309 if _, ok := instructions.children["BUILD"]; ok {
310 instructions.children["BUILD.bazel"] = instructions.children["BUILD"]
311 delete(instructions.children, "BUILD")
312 }
313 }
Cole Faust324a92e2022-08-23 15:29:05 -0700314 }
315 }
316 }
317
Cole Faust358ba4f2023-01-18 15:00:42 -0800318 allEntries := make([]string, 0, len(srcDirMap)+len(buildFilesMap))
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400319 for n := range srcDirMap {
Cole Faust358ba4f2023-01-18 15:00:42 -0800320 allEntries = append(allEntries, n)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200321 }
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400322 for n := range buildFilesMap {
Cole Faust358ba4f2023-01-18 15:00:42 -0800323 if _, ok := srcDirMap[n]; !ok {
324 allEntries = append(allEntries, n)
325 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200326 }
Cole Faust358ba4f2023-01-18 15:00:42 -0800327 // Tests read the error messages generated, so ensure their order is deterministic
328 sort.Strings(allEntries)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200329
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500330 fullForestPath := shared.JoinPath(context.topdir, forestDir)
331 createForestDir := false
332 if fi, err := os.Lstat(fullForestPath); err != nil {
333 if os.IsNotExist(err) {
334 createForestDir = true
335 } else {
336 fmt.Fprintf(os.Stderr, "Could not read info for '%s': %s\n", forestDir, err)
337 }
338 } else if fi.Mode()&os.ModeDir == 0 {
339 if err := os.RemoveAll(fullForestPath); err != nil {
340 fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", forestDir, err)
341 os.Exit(1)
342 }
343 createForestDir = true
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200344 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500345 if createForestDir {
346 if err := os.MkdirAll(fullForestPath, 0777); err != nil {
347 fmt.Fprintf(os.Stderr, "Could not mkdir '%s': %s\n", forestDir, err)
348 os.Exit(1)
349 }
350 context.mkdirCount.Add(1)
351 }
352
353 // Start with a list of items that already exist in the forest, and remove
354 // each element as it is processed in allEntries. Any remaining items in
355 // forestMapForDeletion must be removed. (This handles files which were
356 // removed since the previous forest generation).
357 forestMapForDeletion := readdirToMap(shared.JoinPath(context.topdir, forestDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200358
Cole Faust358ba4f2023-01-18 15:00:42 -0800359 for _, f := range allEntries {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200360 if f[0] == '.' {
361 continue // Ignore dotfiles
362 }
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500363 delete(forestMapForDeletion, f)
364 // todo add deletionCount metric
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200365
366 // The full paths of children in the input trees and in the output tree
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200367 forestChild := shared.JoinPath(forestDir, f)
368 srcChild := shared.JoinPath(srcDir, f)
Cole Faust324a92e2022-08-23 15:29:05 -0700369 if f == "BUILD.bazel" && renamingBuildFile {
370 srcChild = shared.JoinPath(srcDir, "BUILD")
371 }
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200372 buildFilesChild := shared.JoinPath(buildFilesDir, f)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200373
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000374 // Descend in the instruction tree if it exists
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500375 var instructionsChild *instructionsNode
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000376 if instructions != nil {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500377 instructionsChild = instructions.children[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200378 }
379
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200380 srcChildEntry, sExists := srcDirMap[f]
381 buildFilesChildEntry, bExists := buildFilesMap[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200382
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000383 if instructionsChild != nil && instructionsChild.excluded {
Cole Faust324a92e2022-08-23 15:29:05 -0700384 if bExists {
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500385 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
Cole Faust324a92e2022-08-23 15:29:05 -0700386 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200387 continue
388 }
389
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000390 sDir := sExists && isDir(shared.JoinPath(context.topdir, srcChild), srcChildEntry)
391 bDir := bExists && isDir(shared.JoinPath(context.topdir, buildFilesChild), buildFilesChildEntry)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200392
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200393 if !sExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000394 if bDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200395 // Not in the source tree, but we have to exclude something from under
396 // this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000397 context.wg.Add(1)
398 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200399 } else {
400 // Not in the source tree, symlink BUILD file
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500401 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200402 }
403 } else if !bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000404 if sDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200405 // Not in the build file tree, but we have to exclude something from
406 // under this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000407 context.wg.Add(1)
408 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200409 } else {
410 // Not in the build file tree, symlink source tree, carry on
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500411 context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, srcChild))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200412 }
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200413 } else if sDir && bDir {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200414 // Both are directories. Descend.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000415 context.wg.Add(1)
416 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200417 } else if !sDir && !bDir {
Cole Faust324a92e2022-08-23 15:29:05 -0700418 // Neither is a directory. Merge them.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000419 srcBuildFile := shared.JoinPath(context.topdir, srcChild)
420 generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild)
Usta Shrestha783ec5c2022-10-25 01:22:36 -0400421 // The Android.bp file that codegen used to produce `buildFilesChild` is
422 // already a dependency, we can ignore `buildFilesChild`.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000423 context.depCh <- srcChild
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500424 if err := mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose); err != nil {
Cole Faust324a92e2022-08-23 15:29:05 -0700425 fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
426 srcBuildFile, generatedBuildFile, err)
Usta Shrestha071f6c22022-10-25 12:34:06 -0400427 os.Exit(1)
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700428 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200429 } else {
430 // Both exist and one is a file. This is an error.
431 fmt.Fprintf(os.Stderr,
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200432 "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 +0200433 srcChild, buildFilesChild)
Usta Shrestha071f6c22022-10-25 12:34:06 -0400434 os.Exit(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200435 }
436 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200437
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500438 // Remove all files in the forest that exist in neither the source
439 // tree nor the build files tree. (This handles files which were removed
440 // since the previous forest generation).
441 for f := range forestMapForDeletion {
442 var instructionsChild *instructionsNode
443 if instructions != nil {
444 instructionsChild = instructions.children[f]
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000445 }
446
Usta (Tsering) Shresthac4c07b12022-11-08 18:31:14 -0500447 if instructionsChild != nil && instructionsChild.excluded {
448 // This directory may be excluded because bazel writes to it under the
449 // forest root. Thus this path is intentionally left alone.
450 continue
451 }
452 forestChild := shared.JoinPath(context.topdir, forestDir, f)
453 if err := os.RemoveAll(forestChild); err != nil {
454 fmt.Fprintf(os.Stderr, "Failed to remove '%s/%s': %s", forestDir, f, err)
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000455 os.Exit(1)
456 }
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000457 }
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000458}
459
Usta Shresthada15c612022-11-08 14:12:36 -0500460// PlantSymlinkForest Creates a symlink forest by merging the directory tree at "buildFiles" and
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200461// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
462// under srcDir on which readdir() had to be called to produce the symlink
463// forest.
Usta Shresthada15c612022-11-08 14:12:36 -0500464func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) (deps []string, mkdirCount, symlinkCount uint64) {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000465 context := &symlinkForestContext{
Usta Shresthada15c612022-11-08 14:12:36 -0500466 verbose: verbose,
467 topdir: topdir,
468 depCh: make(chan string),
469 mkdirCount: atomic.Uint64{},
470 symlinkCount: atomic.Uint64{},
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000471 }
472
Mark Dacekb2441c52023-10-04 16:12:00 +0000473 // Check whether soong_build has been modified since the last run
474 soongBuildMTime, err := getSoongBuildMTime()
Cole Faust5db92092023-04-05 11:36:23 -0700475 if err != nil {
476 fmt.Fprintln(os.Stderr, err)
477 os.Exit(1)
478 }
479
Mark Dacekb2441c52023-10-04 16:12:00 +0000480 shouldClean, err := shouldCleanSymlinkForest(topdir, forest, soongBuildMTime)
481
482 if err != nil {
483 fmt.Fprintln(os.Stderr, err)
484 os.Exit(1)
485 } else if shouldClean {
486 err = cleanSymlinkForest(topdir, forest)
487 if err != nil {
488 fmt.Fprintln(os.Stderr, err)
489 os.Exit(1)
490 }
491 }
492
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000493 instructions := instructionsFromExcludePathList(exclude)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000494 go func() {
495 context.wg.Add(1)
496 plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
497 context.wg.Wait()
498 close(context.depCh)
499 }()
500
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000501 for dep := range context.depCh {
502 deps = append(deps, dep)
503 }
504
Mark Dacekb2441c52023-10-04 16:12:00 +0000505 err = writeSoongBuildMTimeFile(topdir, forest, soongBuildMTime)
Cole Faust5db92092023-04-05 11:36:23 -0700506 if err != nil {
507 fmt.Fprintln(os.Stderr, err)
508 os.Exit(1)
509 }
Usta Shresthada15c612022-11-08 14:12:36 -0500510 return deps, context.mkdirCount.Load(), context.symlinkCount.Load()
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200511}