blob: 45817e352b62748cad3d8fe09e7662c5bf6a8a0a [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"
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +000023 "sync"
24 "sync/atomic"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020025
26 "android/soong/shared"
27)
28
29// A tree structure that describes what to do at each directory in the created
30// symlink tree. Currently it is used to enumerate which files/directories
31// should be excluded from symlinking. Each instance of "node" represents a file
32// or a directory. If excluded is true, then that file/directory should be
33// excluded from symlinking. Otherwise, the node is not excluded, but one of its
34// descendants is (otherwise the node in question would not exist)
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000035
36type instructionsNode struct {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020037 name string
38 excluded bool // If false, this is just an intermediate node
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000039 children map[string]*instructionsNode
40}
41
42type symlinkForestContext struct {
43 verbose bool
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +000044 topdir string // $TOPDIR
45
46 // State
47 wg sync.WaitGroup
48 depCh chan string
49 okay atomic.Bool // Whether the forest was successfully constructed
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020050}
51
Usta Shresthadb46a9b2022-07-11 11:29:56 -040052// Ensures that the node for the given path exists in the tree and returns it.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000053func ensureNodeExists(root *instructionsNode, path string) *instructionsNode {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020054 if path == "" {
55 return root
56 }
57
58 if path[len(path)-1] == '/' {
59 path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
60 }
61
62 dir, base := filepath.Split(path)
63
64 // First compute the parent node...
65 dn := ensureNodeExists(root, dir)
66
67 // then create the requested node as its direct child, if needed.
68 if child, ok := dn.children[base]; ok {
69 return child
70 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000071 dn.children[base] = &instructionsNode{base, false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020072 return dn.children[base]
73 }
74}
75
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000076// Turns a list of paths to be excluded into a tree
77func instructionsFromExcludePathList(paths []string) *instructionsNode {
78 result := &instructionsNode{"", false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020079
80 for _, p := range paths {
81 ensureNodeExists(result, p).excluded = true
82 }
83
84 return result
85}
86
Cole Faust324a92e2022-08-23 15:29:05 -070087func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error {
88
89 srcBuildFileContent, err := os.ReadFile(srcBuildFile)
90 if err != nil {
91 return err
92 }
93
94 generatedBuildFileContent, err := os.ReadFile(generatedBuildFile)
95 if err != nil {
96 return err
97 }
98
99 // There can't be a package() call in both the source and generated BUILD files.
100 // bp2build will generate a package() call for licensing information, but if
101 // there's no licensing information, it will still generate a package() call
102 // that just sets default_visibility=public. If the handcrafted build file
103 // also has a package() call, we'll allow it to override the bp2build
104 // generated one if it doesn't have any licensing information. If the bp2build
105 // one has licensing information and the handcrafted one exists, we'll leave
106 // them both in for bazel to throw an error.
107 packageRegex := regexp.MustCompile(`(?m)^package\s*\(`)
108 packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`)
109 if packageRegex.Find(srcBuildFileContent) != nil {
110 if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil {
111 fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n",
112 generatedBuildFile, srcBuildFile)
113 }
114 generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
115 }
116
117 outFile, err := os.Create(output)
118 if err != nil {
119 return err
120 }
121
122 _, err = outFile.Write(generatedBuildFileContent)
123 if err != nil {
124 return err
125 }
126
127 if generatedBuildFileContent[len(generatedBuildFileContent)-1] != '\n' {
128 _, err = outFile.WriteString("\n")
129 if err != nil {
130 return err
131 }
132 }
133
134 _, err = outFile.Write(srcBuildFileContent)
135 return err
136}
137
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200138// Calls readdir() and returns it as a map from the basename of the files in dir
139// to os.FileInfo.
140func readdirToMap(dir string) map[string]os.FileInfo {
141 entryList, err := ioutil.ReadDir(dir)
142 result := make(map[string]os.FileInfo)
143
144 if err != nil {
145 if os.IsNotExist(err) {
146 // It's okay if a directory doesn't exist; it just means that one of the
147 // trees to be merged contains parts the other doesn't
148 return result
149 } else {
150 fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
151 os.Exit(1)
152 }
153 }
154
155 for _, fi := range entryList {
156 result[fi.Name()] = fi
157 }
158
159 return result
160}
161
162// Creates a symbolic link at dst pointing to src
163func symlinkIntoForest(topdir, dst, src string) {
164 err := os.Symlink(shared.JoinPath(topdir, src), shared.JoinPath(topdir, dst))
165 if err != nil {
166 fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
167 os.Exit(1)
168 }
169}
170
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200171func isDir(path string, fi os.FileInfo) bool {
172 if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
173 return fi.IsDir()
174 }
175
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000176 fi2, statErr := os.Stat(path)
177 if statErr == nil {
178 return fi2.IsDir()
179 }
180
181 // Check if this is a dangling symlink. If so, treat it like a file, not a dir.
182 _, lstatErr := os.Lstat(path)
183 if lstatErr != nil {
184 fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200185 os.Exit(1)
186 }
187
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000188 return false
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200189}
190
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200191// Recursively plants a symlink forest at forestDir. The symlink tree will
192// contain every file in buildFilesDir and srcDir excluding the files in
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000193// instructions. Collects every directory encountered during the traversal of
194// srcDir .
195func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *instructionsNode, forestDir string, buildFilesDir string, srcDir string) {
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000196 defer context.wg.Done()
197
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000198 if instructions != nil && instructions.excluded {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200199 // This directory is not needed, bail out
200 return
201 }
202
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000203 // We don't add buildFilesDir here because the bp2build files marker files is
204 // already a dependency which covers it. If we ever wanted to turn this into
205 // a generic symlink forest creation tool, we'd need to add it, too.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000206 context.depCh <- srcDir
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000207
208 srcDirMap := readdirToMap(shared.JoinPath(context.topdir, srcDir))
209 buildFilesMap := readdirToMap(shared.JoinPath(context.topdir, buildFilesDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200210
Cole Faust324a92e2022-08-23 15:29:05 -0700211 renamingBuildFile := false
212 if _, ok := srcDirMap["BUILD"]; ok {
213 if _, ok := srcDirMap["BUILD.bazel"]; !ok {
214 if _, ok := buildFilesMap["BUILD.bazel"]; ok {
215 renamingBuildFile = true
216 srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
217 delete(srcDirMap, "BUILD")
218 }
219 }
220 }
221
Usta Shrestha49d04e82022-10-17 17:36:49 -0400222 allEntries := make(map[string]struct{})
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400223 for n := range srcDirMap {
Usta Shrestha49d04e82022-10-17 17:36:49 -0400224 allEntries[n] = struct{}{}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200225 }
226
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400227 for n := range buildFilesMap {
Usta Shrestha49d04e82022-10-17 17:36:49 -0400228 allEntries[n] = struct{}{}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200229 }
230
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000231 err := os.MkdirAll(shared.JoinPath(context.topdir, forestDir), 0777)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200232 if err != nil {
233 fmt.Fprintf(os.Stderr, "Cannot mkdir '%s': %s\n", forestDir, err)
234 os.Exit(1)
235 }
236
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400237 for f := range allEntries {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200238 if f[0] == '.' {
239 continue // Ignore dotfiles
240 }
241
242 // The full paths of children in the input trees and in the output tree
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200243 forestChild := shared.JoinPath(forestDir, f)
244 srcChild := shared.JoinPath(srcDir, f)
Cole Faust324a92e2022-08-23 15:29:05 -0700245 if f == "BUILD.bazel" && renamingBuildFile {
246 srcChild = shared.JoinPath(srcDir, "BUILD")
247 }
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200248 buildFilesChild := shared.JoinPath(buildFilesDir, f)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200249
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000250 // Descend in the instruction tree if it exists
251 var instructionsChild *instructionsNode = nil
252 if instructions != nil {
Cole Faust324a92e2022-08-23 15:29:05 -0700253 if f == "BUILD.bazel" && renamingBuildFile {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000254 instructionsChild = instructions.children["BUILD"]
Cole Faust324a92e2022-08-23 15:29:05 -0700255 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000256 instructionsChild = instructions.children[f]
Cole Faust324a92e2022-08-23 15:29:05 -0700257 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200258 }
259
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200260 srcChildEntry, sExists := srcDirMap[f]
261 buildFilesChildEntry, bExists := buildFilesMap[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200262
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000263 if instructionsChild != nil && instructionsChild.excluded {
Cole Faust324a92e2022-08-23 15:29:05 -0700264 if bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000265 symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
Cole Faust324a92e2022-08-23 15:29:05 -0700266 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200267 continue
268 }
269
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000270 sDir := sExists && isDir(shared.JoinPath(context.topdir, srcChild), srcChildEntry)
271 bDir := bExists && isDir(shared.JoinPath(context.topdir, buildFilesChild), buildFilesChildEntry)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200272
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200273 if !sExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000274 if bDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200275 // Not in the source tree, but we have to exclude something from under
276 // this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000277 context.wg.Add(1)
278 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200279 } else {
280 // Not in the source tree, symlink BUILD file
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000281 symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200282 }
283 } else if !bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000284 if sDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200285 // Not in the build file tree, but we have to exclude something from
286 // under this subtree, so descend
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000287 context.wg.Add(1)
288 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200289 } else {
290 // Not in the build file tree, symlink source tree, carry on
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000291 symlinkIntoForest(context.topdir, forestChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200292 }
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200293 } else if sDir && bDir {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200294 // Both are directories. Descend.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000295 context.wg.Add(1)
296 go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200297 } else if !sDir && !bDir {
Cole Faust324a92e2022-08-23 15:29:05 -0700298 // Neither is a directory. Merge them.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000299 srcBuildFile := shared.JoinPath(context.topdir, srcChild)
300 generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild)
Usta Shrestha783ec5c2022-10-25 01:22:36 -0400301 // The Android.bp file that codegen used to produce `buildFilesChild` is
302 // already a dependency, we can ignore `buildFilesChild`.
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000303 context.depCh <- srcChild
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000304 err = mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose)
Cole Faust324a92e2022-08-23 15:29:05 -0700305 if err != nil {
306 fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
307 srcBuildFile, generatedBuildFile, err)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000308 context.okay.Store(false)
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700309 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200310 } else {
311 // Both exist and one is a file. This is an error.
312 fmt.Fprintf(os.Stderr,
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200313 "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 +0200314 srcChild, buildFilesChild)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000315 context.okay.Store(false)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200316 }
317 }
318}
319
320// Creates a symlink forest by merging the directory tree at "buildFiles" and
321// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
322// under srcDir on which readdir() had to be called to produce the symlink
323// forest.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000324func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) []string {
325 context := &symlinkForestContext{
326 verbose: verbose,
327 topdir: topdir,
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000328 depCh: make(chan string),
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000329 }
330
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000331 context.okay.Store(true)
332
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200333 os.RemoveAll(shared.JoinPath(topdir, forest))
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000334
335 instructions := instructionsFromExcludePathList(exclude)
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000336 go func() {
337 context.wg.Add(1)
338 plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
339 context.wg.Wait()
340 close(context.depCh)
341 }()
342
343 deps := make([]string, 0)
344 for dep := range context.depCh {
345 deps = append(deps, dep)
346 }
347
348 if !context.okay.Load() {
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200349 os.Exit(1)
350 }
Lukacs T. Berki647e7ab2022-10-27 09:55:38 +0000351
352 return deps
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200353}