blob: 81ec7eea73f1325e7baa7a4c09e5ec48f7271c3e [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
49 wg sync.WaitGroup
50 depCh chan string
51 okay atomic.Bool // Whether the forest was successfully constructed
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020052}
53
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +000054// A simple thread pool to limit concurrency on system calls.
55// Necessary because Go spawns a new OS-level thread for each blocking system
56// call. This means that if syscalls are too slow and there are too many of
57// them, the hard limit on OS-level threads can be exhausted.
58type syscallPool struct {
59 shutdownCh []chan<- struct{}
60 workCh chan syscall
61}
62
63type syscall struct {
64 work func()
65 done chan<- struct{}
66}
67
68func createSyscallPool(count int) *syscallPool {
69 result := &syscallPool{
70 shutdownCh: make([]chan<- struct{}, count),
71 workCh: make(chan syscall),
72 }
73
74 for i := 0; i < count; i++ {
75 shutdownCh := make(chan struct{})
76 result.shutdownCh[i] = shutdownCh
77 go result.worker(shutdownCh)
78 }
79
80 return result
81}
82
83func (p *syscallPool) do(work func()) {
84 doneCh := make(chan struct{})
85 p.workCh <- syscall{work, doneCh}
86 <-doneCh
87}
88
89func (p *syscallPool) shutdown() {
90 for _, ch := range p.shutdownCh {
91 ch <- struct{}{} // Blocks until the value is received
92 }
93}
94
95func (p *syscallPool) worker(shutdownCh <-chan struct{}) {
96 for {
97 select {
98 case <-shutdownCh:
99 return
100 case work := <-p.workCh:
101 work.work()
102 work.done <- struct{}{}
103 }
104 }
105}
106
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400107// Ensures that the node for the given path exists in the tree and returns it.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000108func ensureNodeExists(root *instructionsNode, path string) *instructionsNode {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200109 if path == "" {
110 return root
111 }
112
113 if path[len(path)-1] == '/' {
114 path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
115 }
116
117 dir, base := filepath.Split(path)
118
119 // First compute the parent node...
120 dn := ensureNodeExists(root, dir)
121
122 // then create the requested node as its direct child, if needed.
123 if child, ok := dn.children[base]; ok {
124 return child
125 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000126 dn.children[base] = &instructionsNode{base, false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200127 return dn.children[base]
128 }
129}
130
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000131// Turns a list of paths to be excluded into a tree
132func instructionsFromExcludePathList(paths []string) *instructionsNode {
133 result := &instructionsNode{"", false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200134
135 for _, p := range paths {
136 ensureNodeExists(result, p).excluded = true
137 }
138
139 return result
140}
141
Cole Faust324a92e2022-08-23 15:29:05 -0700142func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error {
143
144 srcBuildFileContent, err := os.ReadFile(srcBuildFile)
145 if err != nil {
146 return err
147 }
148
149 generatedBuildFileContent, err := os.ReadFile(generatedBuildFile)
150 if err != nil {
151 return err
152 }
153
154 // There can't be a package() call in both the source and generated BUILD files.
155 // bp2build will generate a package() call for licensing information, but if
156 // there's no licensing information, it will still generate a package() call
157 // that just sets default_visibility=public. If the handcrafted build file
158 // also has a package() call, we'll allow it to override the bp2build
159 // generated one if it doesn't have any licensing information. If the bp2build
160 // one has licensing information and the handcrafted one exists, we'll leave
161 // them both in for bazel to throw an error.
162 packageRegex := regexp.MustCompile(`(?m)^package\s*\(`)
163 packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`)
164 if packageRegex.Find(srcBuildFileContent) != nil {
165 if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil {
166 fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n",
167 generatedBuildFile, srcBuildFile)
168 }
169 generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
170 }
171
172 outFile, err := os.Create(output)
173 if err != nil {
174 return err
175 }
176
177 _, err = outFile.Write(generatedBuildFileContent)
178 if err != nil {
179 return err
180 }
181
182 if generatedBuildFileContent[len(generatedBuildFileContent)-1] != '\n' {
183 _, err = outFile.WriteString("\n")
184 if err != nil {
185 return err
186 }
187 }
188
189 _, err = outFile.Write(srcBuildFileContent)
190 return err
191}
192
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200193// Calls readdir() and returns it as a map from the basename of the files in dir
194// to os.FileInfo.
195func readdirToMap(dir string) map[string]os.FileInfo {
196 entryList, err := ioutil.ReadDir(dir)
197 result := make(map[string]os.FileInfo)
198
199 if err != nil {
200 if os.IsNotExist(err) {
201 // It's okay if a directory doesn't exist; it just means that one of the
202 // trees to be merged contains parts the other doesn't
203 return result
204 } else {
205 fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
206 os.Exit(1)
207 }
208 }
209
210 for _, fi := range entryList {
211 result[fi.Name()] = fi
212 }
213
214 return result
215}
216
217// Creates a symbolic link at dst pointing to src
218func symlinkIntoForest(topdir, dst, src string) {
219 err := os.Symlink(shared.JoinPath(topdir, src), shared.JoinPath(topdir, dst))
220 if err != nil {
221 fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
222 os.Exit(1)
223 }
224}
225
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200226func isDir(path string, fi os.FileInfo) bool {
227 if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
228 return fi.IsDir()
229 }
230
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000231 fi2, statErr := os.Stat(path)
232 if statErr == nil {
233 return fi2.IsDir()
234 }
235
236 // Check if this is a dangling symlink. If so, treat it like a file, not a dir.
237 _, lstatErr := os.Lstat(path)
238 if lstatErr != nil {
239 fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200240 os.Exit(1)
241 }
242
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000243 return false
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200244}
245
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200246// Recursively plants a symlink forest at forestDir. The symlink tree will
247// contain every file in buildFilesDir and srcDir excluding the files in
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000248// instructions. Collects every directory encountered during the traversal of
249// srcDir .
250func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *instructionsNode, forestDir string, buildFilesDir string, srcDir string) {
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000251 defer context.wg.Done()
252
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000253 if instructions != nil && instructions.excluded {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200254 // This directory is not needed, bail out
255 return
256 }
257
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000258 // We don't add buildFilesDir here because the bp2build files marker files is
259 // already a dependency which covers it. If we ever wanted to turn this into
260 // a generic symlink forest creation tool, we'd need to add it, too.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000261 context.depCh <- srcDir
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000262
263 srcDirMap := readdirToMap(shared.JoinPath(context.topdir, srcDir))
264 buildFilesMap := readdirToMap(shared.JoinPath(context.topdir, buildFilesDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200265
Cole Faust324a92e2022-08-23 15:29:05 -0700266 renamingBuildFile := false
267 if _, ok := srcDirMap["BUILD"]; ok {
268 if _, ok := srcDirMap["BUILD.bazel"]; !ok {
269 if _, ok := buildFilesMap["BUILD.bazel"]; ok {
270 renamingBuildFile = true
271 srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
272 delete(srcDirMap, "BUILD")
273 }
274 }
275 }
276
Usta Shrestha49d04e82022-10-17 17:36:49 -0400277 allEntries := make(map[string]struct{})
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400278 for n := range srcDirMap {
Usta Shrestha49d04e82022-10-17 17:36:49 -0400279 allEntries[n] = struct{}{}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200280 }
281
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400282 for n := range buildFilesMap {
Usta Shrestha49d04e82022-10-17 17:36:49 -0400283 allEntries[n] = struct{}{}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200284 }
285
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000286 err := os.MkdirAll(shared.JoinPath(context.topdir, forestDir), 0777)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200287 if err != nil {
288 fmt.Fprintf(os.Stderr, "Cannot mkdir '%s': %s\n", forestDir, err)
289 os.Exit(1)
290 }
291
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400292 for f := range allEntries {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200293 if f[0] == '.' {
294 continue // Ignore dotfiles
295 }
296
297 // The full paths of children in the input trees and in the output tree
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200298 forestChild := shared.JoinPath(forestDir, f)
299 srcChild := shared.JoinPath(srcDir, f)
Cole Faust324a92e2022-08-23 15:29:05 -0700300 if f == "BUILD.bazel" && renamingBuildFile {
301 srcChild = shared.JoinPath(srcDir, "BUILD")
302 }
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200303 buildFilesChild := shared.JoinPath(buildFilesDir, f)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200304
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000305 // Descend in the instruction tree if it exists
306 var instructionsChild *instructionsNode = nil
307 if instructions != nil {
Cole Faust324a92e2022-08-23 15:29:05 -0700308 if f == "BUILD.bazel" && renamingBuildFile {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000309 instructionsChild = instructions.children["BUILD"]
Cole Faust324a92e2022-08-23 15:29:05 -0700310 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000311 instructionsChild = instructions.children[f]
Cole Faust324a92e2022-08-23 15:29:05 -0700312 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200313 }
314
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200315 srcChildEntry, sExists := srcDirMap[f]
316 buildFilesChildEntry, bExists := buildFilesMap[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200317
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000318 if instructionsChild != nil && instructionsChild.excluded {
Cole Faust324a92e2022-08-23 15:29:05 -0700319 if bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000320 symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
Cole Faust324a92e2022-08-23 15:29:05 -0700321 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200322 continue
323 }
324
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000325 sDir := sExists && isDir(shared.JoinPath(context.topdir, srcChild), srcChildEntry)
326 bDir := bExists && isDir(shared.JoinPath(context.topdir, buildFilesChild), buildFilesChildEntry)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200327
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200328 if !sExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000329 if bDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200330 // Not in the source tree, but we have to exclude something from under
331 // this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000332 context.wg.Add(1)
333 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200334 } else {
335 // Not in the source tree, symlink BUILD file
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000336 symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200337 }
338 } else if !bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000339 if sDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200340 // Not in the build file tree, but we have to exclude something from
341 // under this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000342 context.wg.Add(1)
343 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200344 } else {
345 // Not in the build file tree, symlink source tree, carry on
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000346 symlinkIntoForest(context.topdir, forestChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200347 }
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200348 } else if sDir && bDir {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200349 // Both are directories. Descend.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000350 context.wg.Add(1)
351 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200352 } else if !sDir && !bDir {
Cole Faust324a92e2022-08-23 15:29:05 -0700353 // Neither is a directory. Merge them.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000354 srcBuildFile := shared.JoinPath(context.topdir, srcChild)
355 generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild)
Usta Shrestha783ec5c2022-10-25 01:22:36 -0400356 // The Android.bp file that codegen used to produce `buildFilesChild` is
357 // already a dependency, we can ignore `buildFilesChild`.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000358 context.depCh <- srcChild
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000359 err = mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose)
Cole Faust324a92e2022-08-23 15:29:05 -0700360 if err != nil {
361 fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
362 srcBuildFile, generatedBuildFile, err)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000363 context.okay.Store(false)
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700364 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200365 } else {
366 // Both exist and one is a file. This is an error.
367 fmt.Fprintf(os.Stderr,
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200368 "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 +0200369 srcChild, buildFilesChild)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000370 context.okay.Store(false)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200371 }
372 }
373}
374
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000375func removeParallelRecursive(pool *syscallPool, path string, fi os.FileInfo, wg *sync.WaitGroup) {
376 defer wg.Done()
377
378 if fi.IsDir() {
379 children := readdirToMap(path)
380 childrenWg := &sync.WaitGroup{}
381 childrenWg.Add(len(children))
382
383 for child, childFi := range children {
384 go removeParallelRecursive(pool, shared.JoinPath(path, child), childFi, childrenWg)
385 }
386
387 childrenWg.Wait()
388 }
389
390 pool.do(func() {
391 if err := os.Remove(path); err != nil {
392 fmt.Fprintf(os.Stderr, "Cannot unlink '%s': %s\n", path, err)
393 os.Exit(1)
394 }
395 })
396}
397
398func removeParallel(path string) {
399 fi, err := os.Lstat(path)
400 if err != nil {
401 if errors.Is(err, fs.ErrNotExist) {
402 return
403 }
404
405 fmt.Fprintf(os.Stderr, "Cannot lstat '%s': %s\n", path, err)
406 os.Exit(1)
407 }
408
409 wg := &sync.WaitGroup{}
410 wg.Add(1)
411
412 // Random guess as to the best number of syscalls to run in parallel
413 pool := createSyscallPool(100)
414 removeParallelRecursive(pool, path, fi, wg)
415 pool.shutdown()
416
417 wg.Wait()
418}
419
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200420// Creates a symlink forest by merging the directory tree at "buildFiles" and
421// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
422// under srcDir on which readdir() had to be called to produce the symlink
423// forest.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000424func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) []string {
425 context := &symlinkForestContext{
426 verbose: verbose,
427 topdir: topdir,
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000428 depCh: make(chan string),
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000429 }
430
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000431 context.okay.Store(true)
432
Lukacs T. Berkibc5f7312022-10-31 09:01:34 +0000433 removeParallel(shared.JoinPath(topdir, forest))
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000434
435 instructions := instructionsFromExcludePathList(exclude)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000436 go func() {
437 context.wg.Add(1)
438 plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
439 context.wg.Wait()
440 close(context.depCh)
441 }()
442
443 deps := make([]string, 0)
444 for dep := range context.depCh {
445 deps = append(deps, dep)
446 }
447
448 if !context.okay.Load() {
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200449 os.Exit(1)
450 }
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000451
452 return deps
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200453}