blob: 4a4ae9e0c244e2b4b4233ed71283f94291dbfc54 [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 (
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +000018 "errors"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020019 "fmt"
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +000020 "io/fs"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020021 "io/ioutil"
22 "os"
23 "path/filepath"
Cole Faust324a92e2022-08-23 15:29:05 -070024 "regexp"
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"
29)
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
53 okay atomic.Bool // Whether the forest was successfully constructed
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020054}
55
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +000056// A simple thread pool to limit concurrency on system calls.
57// Necessary because Go spawns a new OS-level thread for each blocking system
58// call. This means that if syscalls are too slow and there are too many of
59// them, the hard limit on OS-level threads can be exhausted.
60type syscallPool struct {
61 shutdownCh []chan<- struct{}
62 workCh chan syscall
63}
64
65type syscall struct {
66 work func()
67 done chan<- struct{}
68}
69
70func createSyscallPool(count int) *syscallPool {
71 result := &syscallPool{
72 shutdownCh: make([]chan<- struct{}, count),
73 workCh: make(chan syscall),
74 }
75
76 for i := 0; i < count; i++ {
77 shutdownCh := make(chan struct{})
78 result.shutdownCh[i] = shutdownCh
79 go result.worker(shutdownCh)
80 }
81
82 return result
83}
84
85func (p *syscallPool) do(work func()) {
86 doneCh := make(chan struct{})
87 p.workCh <- syscall{work, doneCh}
88 <-doneCh
89}
90
91func (p *syscallPool) shutdown() {
92 for _, ch := range p.shutdownCh {
93 ch <- struct{}{} // Blocks until the value is received
94 }
95}
96
97func (p *syscallPool) worker(shutdownCh <-chan struct{}) {
98 for {
99 select {
100 case <-shutdownCh:
101 return
102 case work := <-p.workCh:
103 work.work()
104 work.done <- struct{}{}
105 }
106 }
107}
108
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400109// Ensures that the node for the given path exists in the tree and returns it.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000110func ensureNodeExists(root *instructionsNode, path string) *instructionsNode {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200111 if path == "" {
112 return root
113 }
114
115 if path[len(path)-1] == '/' {
116 path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
117 }
118
119 dir, base := filepath.Split(path)
120
121 // First compute the parent node...
122 dn := ensureNodeExists(root, dir)
123
124 // then create the requested node as its direct child, if needed.
125 if child, ok := dn.children[base]; ok {
126 return child
127 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000128 dn.children[base] = &instructionsNode{base, false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200129 return dn.children[base]
130 }
131}
132
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000133// Turns a list of paths to be excluded into a tree
134func instructionsFromExcludePathList(paths []string) *instructionsNode {
135 result := &instructionsNode{"", false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200136
137 for _, p := range paths {
138 ensureNodeExists(result, p).excluded = true
139 }
140
141 return result
142}
143
Cole Faust324a92e2022-08-23 15:29:05 -0700144func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error {
145
146 srcBuildFileContent, err := os.ReadFile(srcBuildFile)
147 if err != nil {
148 return err
149 }
150
151 generatedBuildFileContent, err := os.ReadFile(generatedBuildFile)
152 if err != nil {
153 return err
154 }
155
156 // There can't be a package() call in both the source and generated BUILD files.
157 // bp2build will generate a package() call for licensing information, but if
158 // there's no licensing information, it will still generate a package() call
159 // that just sets default_visibility=public. If the handcrafted build file
160 // also has a package() call, we'll allow it to override the bp2build
161 // generated one if it doesn't have any licensing information. If the bp2build
162 // one has licensing information and the handcrafted one exists, we'll leave
163 // them both in for bazel to throw an error.
164 packageRegex := regexp.MustCompile(`(?m)^package\s*\(`)
165 packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`)
166 if packageRegex.Find(srcBuildFileContent) != nil {
167 if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil {
168 fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n",
169 generatedBuildFile, srcBuildFile)
170 }
171 generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
172 }
173
174 outFile, err := os.Create(output)
175 if err != nil {
176 return err
177 }
178
179 _, err = outFile.Write(generatedBuildFileContent)
180 if err != nil {
181 return err
182 }
183
184 if generatedBuildFileContent[len(generatedBuildFileContent)-1] != '\n' {
185 _, err = outFile.WriteString("\n")
186 if err != nil {
187 return err
188 }
189 }
190
191 _, err = outFile.Write(srcBuildFileContent)
192 return err
193}
194
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200195// Calls readdir() and returns it as a map from the basename of the files in dir
196// to os.FileInfo.
197func readdirToMap(dir string) map[string]os.FileInfo {
198 entryList, err := ioutil.ReadDir(dir)
199 result := make(map[string]os.FileInfo)
200
201 if err != nil {
202 if os.IsNotExist(err) {
203 // It's okay if a directory doesn't exist; it just means that one of the
204 // trees to be merged contains parts the other doesn't
205 return result
206 } else {
207 fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
208 os.Exit(1)
209 }
210 }
211
212 for _, fi := range entryList {
213 result[fi.Name()] = fi
214 }
215
216 return result
217}
218
219// Creates a symbolic link at dst pointing to src
220func symlinkIntoForest(topdir, dst, src string) {
221 err := os.Symlink(shared.JoinPath(topdir, src), shared.JoinPath(topdir, dst))
222 if err != nil {
223 fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
224 os.Exit(1)
225 }
226}
227
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200228func isDir(path string, fi os.FileInfo) bool {
229 if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
230 return fi.IsDir()
231 }
232
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000233 fi2, statErr := os.Stat(path)
234 if statErr == nil {
235 return fi2.IsDir()
236 }
237
238 // Check if this is a dangling symlink. If so, treat it like a file, not a dir.
239 _, lstatErr := os.Lstat(path)
240 if lstatErr != nil {
241 fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200242 os.Exit(1)
243 }
244
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000245 return false
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200246}
247
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200248// Recursively plants a symlink forest at forestDir. The symlink tree will
249// contain every file in buildFilesDir and srcDir excluding the files in
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000250// instructions. Collects every directory encountered during the traversal of
251// srcDir .
252func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *instructionsNode, forestDir string, buildFilesDir string, srcDir string) {
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000253 defer context.wg.Done()
254
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000255 if instructions != nil && instructions.excluded {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200256 // This directory is not needed, bail out
257 return
258 }
259
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000260 // We don't add buildFilesDir here because the bp2build files marker files is
261 // already a dependency which covers it. If we ever wanted to turn this into
262 // a generic symlink forest creation tool, we'd need to add it, too.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000263 context.depCh <- srcDir
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000264
265 srcDirMap := readdirToMap(shared.JoinPath(context.topdir, srcDir))
266 buildFilesMap := readdirToMap(shared.JoinPath(context.topdir, buildFilesDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200267
Cole Faust324a92e2022-08-23 15:29:05 -0700268 renamingBuildFile := false
269 if _, ok := srcDirMap["BUILD"]; ok {
270 if _, ok := srcDirMap["BUILD.bazel"]; !ok {
271 if _, ok := buildFilesMap["BUILD.bazel"]; ok {
272 renamingBuildFile = true
273 srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
274 delete(srcDirMap, "BUILD")
275 }
276 }
277 }
278
Usta Shrestha49d04e82022-10-17 17:36:49 -0400279 allEntries := make(map[string]struct{})
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400280 for n := range srcDirMap {
Usta Shrestha49d04e82022-10-17 17:36:49 -0400281 allEntries[n] = struct{}{}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200282 }
283
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400284 for n := range buildFilesMap {
Usta Shrestha49d04e82022-10-17 17:36:49 -0400285 allEntries[n] = struct{}{}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200286 }
287
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000288 err := os.MkdirAll(shared.JoinPath(context.topdir, forestDir), 0777)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200289 if err != nil {
290 fmt.Fprintf(os.Stderr, "Cannot mkdir '%s': %s\n", forestDir, err)
291 os.Exit(1)
292 }
Usta Shresthada15c612022-11-08 14:12:36 -0500293 context.mkdirCount.Add(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200294
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400295 for f := range allEntries {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200296 if f[0] == '.' {
297 continue // Ignore dotfiles
298 }
299
300 // The full paths of children in the input trees and in the output tree
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200301 forestChild := shared.JoinPath(forestDir, f)
302 srcChild := shared.JoinPath(srcDir, f)
Cole Faust324a92e2022-08-23 15:29:05 -0700303 if f == "BUILD.bazel" && renamingBuildFile {
304 srcChild = shared.JoinPath(srcDir, "BUILD")
305 }
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200306 buildFilesChild := shared.JoinPath(buildFilesDir, f)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200307
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000308 // Descend in the instruction tree if it exists
309 var instructionsChild *instructionsNode = nil
310 if instructions != nil {
Cole Faust324a92e2022-08-23 15:29:05 -0700311 if f == "BUILD.bazel" && renamingBuildFile {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000312 instructionsChild = instructions.children["BUILD"]
Cole Faust324a92e2022-08-23 15:29:05 -0700313 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000314 instructionsChild = instructions.children[f]
Cole Faust324a92e2022-08-23 15:29:05 -0700315 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200316 }
317
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200318 srcChildEntry, sExists := srcDirMap[f]
319 buildFilesChildEntry, bExists := buildFilesMap[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200320
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000321 if instructionsChild != nil && instructionsChild.excluded {
Cole Faust324a92e2022-08-23 15:29:05 -0700322 if bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000323 symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
Usta Shresthada15c612022-11-08 14:12:36 -0500324 context.symlinkCount.Add(1)
Cole Faust324a92e2022-08-23 15:29:05 -0700325 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200326 continue
327 }
328
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000329 sDir := sExists && isDir(shared.JoinPath(context.topdir, srcChild), srcChildEntry)
330 bDir := bExists && isDir(shared.JoinPath(context.topdir, buildFilesChild), buildFilesChildEntry)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200331
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200332 if !sExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000333 if bDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200334 // Not in the source tree, but we have to exclude something from under
335 // this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000336 context.wg.Add(1)
337 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200338 } else {
339 // Not in the source tree, symlink BUILD file
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000340 symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
Usta Shresthada15c612022-11-08 14:12:36 -0500341 context.symlinkCount.Add(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200342 }
343 } else if !bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000344 if sDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200345 // Not in the build file tree, but we have to exclude something from
346 // under this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000347 context.wg.Add(1)
348 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200349 } else {
350 // Not in the build file tree, symlink source tree, carry on
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000351 symlinkIntoForest(context.topdir, forestChild, srcChild)
Usta Shresthada15c612022-11-08 14:12:36 -0500352 context.symlinkCount.Add(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200353 }
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200354 } else if sDir && bDir {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200355 // Both are directories. Descend.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000356 context.wg.Add(1)
357 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200358 } else if !sDir && !bDir {
Cole Faust324a92e2022-08-23 15:29:05 -0700359 // Neither is a directory. Merge them.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000360 srcBuildFile := shared.JoinPath(context.topdir, srcChild)
361 generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild)
Usta Shrestha783ec5c2022-10-25 01:22:36 -0400362 // The Android.bp file that codegen used to produce `buildFilesChild` is
363 // already a dependency, we can ignore `buildFilesChild`.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000364 context.depCh <- srcChild
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000365 err = mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose)
Cole Faust324a92e2022-08-23 15:29:05 -0700366 if err != nil {
367 fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
368 srcBuildFile, generatedBuildFile, err)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000369 context.okay.Store(false)
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700370 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200371 } else {
372 // Both exist and one is a file. This is an error.
373 fmt.Fprintf(os.Stderr,
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200374 "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 +0200375 srcChild, buildFilesChild)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000376 context.okay.Store(false)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200377 }
378 }
379}
380
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000381func removeParallelRecursive(pool *syscallPool, path string, fi os.FileInfo, wg *sync.WaitGroup) {
382 defer wg.Done()
383
384 if fi.IsDir() {
385 children := readdirToMap(path)
386 childrenWg := &sync.WaitGroup{}
387 childrenWg.Add(len(children))
388
389 for child, childFi := range children {
390 go removeParallelRecursive(pool, shared.JoinPath(path, child), childFi, childrenWg)
391 }
392
393 childrenWg.Wait()
394 }
395
396 pool.do(func() {
397 if err := os.Remove(path); err != nil {
398 fmt.Fprintf(os.Stderr, "Cannot unlink '%s': %s\n", path, err)
399 os.Exit(1)
400 }
401 })
402}
403
404func removeParallel(path string) {
405 fi, err := os.Lstat(path)
406 if err != nil {
407 if errors.Is(err, fs.ErrNotExist) {
408 return
409 }
410
411 fmt.Fprintf(os.Stderr, "Cannot lstat '%s': %s\n", path, err)
412 os.Exit(1)
413 }
414
415 wg := &sync.WaitGroup{}
416 wg.Add(1)
417
418 // Random guess as to the best number of syscalls to run in parallel
419 pool := createSyscallPool(100)
420 removeParallelRecursive(pool, path, fi, wg)
421 pool.shutdown()
422
423 wg.Wait()
424}
425
Usta Shresthada15c612022-11-08 14:12:36 -0500426// PlantSymlinkForest Creates a symlink forest by merging the directory tree at "buildFiles" and
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200427// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
428// under srcDir on which readdir() had to be called to produce the symlink
429// forest.
Usta Shresthada15c612022-11-08 14:12:36 -0500430func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) (deps []string, mkdirCount, symlinkCount uint64) {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000431 context := &symlinkForestContext{
Usta Shresthada15c612022-11-08 14:12:36 -0500432 verbose: verbose,
433 topdir: topdir,
434 depCh: make(chan string),
435 mkdirCount: atomic.Uint64{},
436 symlinkCount: atomic.Uint64{},
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000437 }
438
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000439 context.okay.Store(true)
440
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000441 removeParallel(shared.JoinPath(topdir, forest))
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000442
443 instructions := instructionsFromExcludePathList(exclude)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000444 go func() {
445 context.wg.Add(1)
446 plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
447 context.wg.Wait()
448 close(context.depCh)
449 }()
450
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000451 for dep := range context.depCh {
452 deps = append(deps, dep)
453 }
454
455 if !context.okay.Load() {
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200456 os.Exit(1)
457 }
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000458
Usta Shresthada15c612022-11-08 14:12:36 -0500459 return deps, context.mkdirCount.Load(), context.symlinkCount.Load()
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200460}