blob: 37188f196f4a251b1bac875bb63d33365a7183d3 [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"
Cole Faust358ba4f2023-01-18 15:00:42 -080025 "sort"
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"
30)
31
32// A tree structure that describes what to do at each directory in the created
33// symlink tree. Currently it is used to enumerate which files/directories
34// should be excluded from symlinking. Each instance of "node" represents a file
35// or a directory. If excluded is true, then that file/directory should be
36// excluded from symlinking. Otherwise, the node is not excluded, but one of its
37// descendants is (otherwise the node in question would not exist)
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000038
39type instructionsNode struct {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020040 name string
41 excluded bool // If false, this is just an intermediate node
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000042 children map[string]*instructionsNode
43}
44
45type symlinkForestContext struct {
46 verbose bool
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +000047 topdir string // $TOPDIR
48
49 // State
Usta Shresthada15c612022-11-08 14:12:36 -050050 wg sync.WaitGroup
51 depCh chan string
52 mkdirCount atomic.Uint64
53 symlinkCount atomic.Uint64
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020054}
55
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
Cole Faust358ba4f2023-01-18 15:00:42 -0800279 allEntries := make([]string, 0, len(srcDirMap)+len(buildFilesMap))
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400280 for n := range srcDirMap {
Cole Faust358ba4f2023-01-18 15:00:42 -0800281 allEntries = append(allEntries, n)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200282 }
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400283 for n := range buildFilesMap {
Cole Faust358ba4f2023-01-18 15:00:42 -0800284 if _, ok := srcDirMap[n]; !ok {
285 allEntries = append(allEntries, n)
286 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200287 }
Cole Faust358ba4f2023-01-18 15:00:42 -0800288 // Tests read the error messages generated, so ensure their order is deterministic
289 sort.Strings(allEntries)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200290
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000291 err := os.MkdirAll(shared.JoinPath(context.topdir, forestDir), 0777)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200292 if err != nil {
293 fmt.Fprintf(os.Stderr, "Cannot mkdir '%s': %s\n", forestDir, err)
294 os.Exit(1)
295 }
Usta Shresthada15c612022-11-08 14:12:36 -0500296 context.mkdirCount.Add(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200297
Cole Faust358ba4f2023-01-18 15:00:42 -0800298 for _, f := range allEntries {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200299 if f[0] == '.' {
300 continue // Ignore dotfiles
301 }
302
303 // The full paths of children in the input trees and in the output tree
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200304 forestChild := shared.JoinPath(forestDir, f)
305 srcChild := shared.JoinPath(srcDir, f)
Cole Faust324a92e2022-08-23 15:29:05 -0700306 if f == "BUILD.bazel" && renamingBuildFile {
307 srcChild = shared.JoinPath(srcDir, "BUILD")
308 }
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200309 buildFilesChild := shared.JoinPath(buildFilesDir, f)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200310
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000311 // Descend in the instruction tree if it exists
312 var instructionsChild *instructionsNode = nil
313 if instructions != nil {
Cole Faust324a92e2022-08-23 15:29:05 -0700314 if f == "BUILD.bazel" && renamingBuildFile {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000315 instructionsChild = instructions.children["BUILD"]
Cole Faust324a92e2022-08-23 15:29:05 -0700316 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000317 instructionsChild = instructions.children[f]
Cole Faust324a92e2022-08-23 15:29:05 -0700318 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200319 }
320
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200321 srcChildEntry, sExists := srcDirMap[f]
322 buildFilesChildEntry, bExists := buildFilesMap[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200323
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000324 if instructionsChild != nil && instructionsChild.excluded {
Cole Faust324a92e2022-08-23 15:29:05 -0700325 if bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000326 symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
Usta Shresthada15c612022-11-08 14:12:36 -0500327 context.symlinkCount.Add(1)
Cole Faust324a92e2022-08-23 15:29:05 -0700328 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200329 continue
330 }
331
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000332 sDir := sExists && isDir(shared.JoinPath(context.topdir, srcChild), srcChildEntry)
333 bDir := bExists && isDir(shared.JoinPath(context.topdir, buildFilesChild), buildFilesChildEntry)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200334
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200335 if !sExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000336 if bDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200337 // Not in the source tree, but we have to exclude something from under
338 // this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000339 context.wg.Add(1)
340 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200341 } else {
342 // Not in the source tree, symlink BUILD file
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000343 symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
Usta Shresthada15c612022-11-08 14:12:36 -0500344 context.symlinkCount.Add(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200345 }
346 } else if !bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000347 if sDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200348 // Not in the build file tree, but we have to exclude something from
349 // under this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000350 context.wg.Add(1)
351 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200352 } else {
353 // Not in the build file tree, symlink source tree, carry on
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000354 symlinkIntoForest(context.topdir, forestChild, srcChild)
Usta Shresthada15c612022-11-08 14:12:36 -0500355 context.symlinkCount.Add(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200356 }
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200357 } else if sDir && bDir {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200358 // Both are directories. Descend.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000359 context.wg.Add(1)
360 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200361 } else if !sDir && !bDir {
Cole Faust324a92e2022-08-23 15:29:05 -0700362 // Neither is a directory. Merge them.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000363 srcBuildFile := shared.JoinPath(context.topdir, srcChild)
364 generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild)
Usta Shrestha783ec5c2022-10-25 01:22:36 -0400365 // The Android.bp file that codegen used to produce `buildFilesChild` is
366 // already a dependency, we can ignore `buildFilesChild`.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000367 context.depCh <- srcChild
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000368 err = mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose)
Cole Faust324a92e2022-08-23 15:29:05 -0700369 if err != nil {
370 fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
371 srcBuildFile, generatedBuildFile, err)
Usta Shrestha071f6c22022-10-25 12:34:06 -0400372 os.Exit(1)
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700373 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200374 } else {
375 // Both exist and one is a file. This is an error.
376 fmt.Fprintf(os.Stderr,
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200377 "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 +0200378 srcChild, buildFilesChild)
Usta Shrestha071f6c22022-10-25 12:34:06 -0400379 os.Exit(1)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200380 }
381 }
382}
383
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000384func removeParallelRecursive(pool *syscallPool, path string, fi os.FileInfo, wg *sync.WaitGroup) {
385 defer wg.Done()
386
387 if fi.IsDir() {
388 children := readdirToMap(path)
389 childrenWg := &sync.WaitGroup{}
390 childrenWg.Add(len(children))
391
392 for child, childFi := range children {
393 go removeParallelRecursive(pool, shared.JoinPath(path, child), childFi, childrenWg)
394 }
395
396 childrenWg.Wait()
397 }
398
399 pool.do(func() {
400 if err := os.Remove(path); err != nil {
401 fmt.Fprintf(os.Stderr, "Cannot unlink '%s': %s\n", path, err)
402 os.Exit(1)
403 }
404 })
405}
406
407func removeParallel(path string) {
408 fi, err := os.Lstat(path)
409 if err != nil {
410 if errors.Is(err, fs.ErrNotExist) {
411 return
412 }
413
414 fmt.Fprintf(os.Stderr, "Cannot lstat '%s': %s\n", path, err)
415 os.Exit(1)
416 }
417
418 wg := &sync.WaitGroup{}
419 wg.Add(1)
420
421 // Random guess as to the best number of syscalls to run in parallel
422 pool := createSyscallPool(100)
423 removeParallelRecursive(pool, path, fi, wg)
424 pool.shutdown()
425
426 wg.Wait()
427}
428
Usta Shresthada15c612022-11-08 14:12:36 -0500429// PlantSymlinkForest Creates a symlink forest by merging the directory tree at "buildFiles" and
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200430// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
431// under srcDir on which readdir() had to be called to produce the symlink
432// forest.
Usta Shresthada15c612022-11-08 14:12:36 -0500433func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) (deps []string, mkdirCount, symlinkCount uint64) {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000434 context := &symlinkForestContext{
Usta Shresthada15c612022-11-08 14:12:36 -0500435 verbose: verbose,
436 topdir: topdir,
437 depCh: make(chan string),
438 mkdirCount: atomic.Uint64{},
439 symlinkCount: atomic.Uint64{},
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000440 }
441
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000442 removeParallel(shared.JoinPath(topdir, forest))
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000443
444 instructions := instructionsFromExcludePathList(exclude)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000445 go func() {
446 context.wg.Add(1)
447 plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
448 context.wg.Wait()
449 close(context.depCh)
450 }()
451
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000452 for dep := range context.depCh {
453 deps = append(deps, dep)
454 }
455
Usta Shresthada15c612022-11-08 14:12:36 -0500456 return deps, context.mkdirCount.Load(), context.symlinkCount.Load()
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200457}