blob: e2b99c438bd5e3ab584059f312fb384e4a140c74 [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. Berkib353cca2021-04-16 13:47:36 +020023
24 "android/soong/shared"
25)
26
27// A tree structure that describes what to do at each directory in the created
28// symlink tree. Currently it is used to enumerate which files/directories
29// should be excluded from symlinking. Each instance of "node" represents a file
30// or a directory. If excluded is true, then that file/directory should be
31// excluded from symlinking. Otherwise, the node is not excluded, but one of its
32// descendants is (otherwise the node in question would not exist)
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000033
34type instructionsNode struct {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020035 name string
36 excluded bool // If false, this is just an intermediate node
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000037 children map[string]*instructionsNode
38}
39
40type symlinkForestContext struct {
41 verbose bool
42 topdir string // $TOPDIR
43 deps []string // Files/directories read while constructing the forest
44 okay bool // Whether the forest was successfully constructed
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020045}
46
Usta Shresthadb46a9b2022-07-11 11:29:56 -040047// Ensures that the node for the given path exists in the tree and returns it.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000048func ensureNodeExists(root *instructionsNode, path string) *instructionsNode {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020049 if path == "" {
50 return root
51 }
52
53 if path[len(path)-1] == '/' {
54 path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
55 }
56
57 dir, base := filepath.Split(path)
58
59 // First compute the parent node...
60 dn := ensureNodeExists(root, dir)
61
62 // then create the requested node as its direct child, if needed.
63 if child, ok := dn.children[base]; ok {
64 return child
65 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000066 dn.children[base] = &instructionsNode{base, false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020067 return dn.children[base]
68 }
69}
70
Lukacs T. Berkic541cd22022-10-26 07:26:50 +000071// Turns a list of paths to be excluded into a tree
72func instructionsFromExcludePathList(paths []string) *instructionsNode {
73 result := &instructionsNode{"", false, make(map[string]*instructionsNode)}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020074
75 for _, p := range paths {
76 ensureNodeExists(result, p).excluded = true
77 }
78
79 return result
80}
81
Cole Faust324a92e2022-08-23 15:29:05 -070082func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error {
83
84 srcBuildFileContent, err := os.ReadFile(srcBuildFile)
85 if err != nil {
86 return err
87 }
88
89 generatedBuildFileContent, err := os.ReadFile(generatedBuildFile)
90 if err != nil {
91 return err
92 }
93
94 // There can't be a package() call in both the source and generated BUILD files.
95 // bp2build will generate a package() call for licensing information, but if
96 // there's no licensing information, it will still generate a package() call
97 // that just sets default_visibility=public. If the handcrafted build file
98 // also has a package() call, we'll allow it to override the bp2build
99 // generated one if it doesn't have any licensing information. If the bp2build
100 // one has licensing information and the handcrafted one exists, we'll leave
101 // them both in for bazel to throw an error.
102 packageRegex := regexp.MustCompile(`(?m)^package\s*\(`)
103 packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`)
104 if packageRegex.Find(srcBuildFileContent) != nil {
105 if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil {
106 fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n",
107 generatedBuildFile, srcBuildFile)
108 }
109 generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
110 }
111
112 outFile, err := os.Create(output)
113 if err != nil {
114 return err
115 }
116
117 _, err = outFile.Write(generatedBuildFileContent)
118 if err != nil {
119 return err
120 }
121
122 if generatedBuildFileContent[len(generatedBuildFileContent)-1] != '\n' {
123 _, err = outFile.WriteString("\n")
124 if err != nil {
125 return err
126 }
127 }
128
129 _, err = outFile.Write(srcBuildFileContent)
130 return err
131}
132
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200133// Calls readdir() and returns it as a map from the basename of the files in dir
134// to os.FileInfo.
135func readdirToMap(dir string) map[string]os.FileInfo {
136 entryList, err := ioutil.ReadDir(dir)
137 result := make(map[string]os.FileInfo)
138
139 if err != nil {
140 if os.IsNotExist(err) {
141 // It's okay if a directory doesn't exist; it just means that one of the
142 // trees to be merged contains parts the other doesn't
143 return result
144 } else {
145 fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
146 os.Exit(1)
147 }
148 }
149
150 for _, fi := range entryList {
151 result[fi.Name()] = fi
152 }
153
154 return result
155}
156
157// Creates a symbolic link at dst pointing to src
158func symlinkIntoForest(topdir, dst, src string) {
159 err := os.Symlink(shared.JoinPath(topdir, src), shared.JoinPath(topdir, dst))
160 if err != nil {
161 fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
162 os.Exit(1)
163 }
164}
165
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200166func isDir(path string, fi os.FileInfo) bool {
167 if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
168 return fi.IsDir()
169 }
170
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000171 fi2, statErr := os.Stat(path)
172 if statErr == nil {
173 return fi2.IsDir()
174 }
175
176 // Check if this is a dangling symlink. If so, treat it like a file, not a dir.
177 _, lstatErr := os.Lstat(path)
178 if lstatErr != nil {
179 fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200180 os.Exit(1)
181 }
182
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000183 return false
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200184}
185
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200186// Recursively plants a symlink forest at forestDir. The symlink tree will
187// contain every file in buildFilesDir and srcDir excluding the files in
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000188// instructions. Collects every directory encountered during the traversal of
189// srcDir .
190func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *instructionsNode, forestDir string, buildFilesDir string, srcDir string) {
191 if instructions != nil && instructions.excluded {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200192 // This directory is not needed, bail out
193 return
194 }
195
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000196 // We don't add buildFilesDir here because the bp2build files marker files is
197 // already a dependency which covers it. If we ever wanted to turn this into
198 // a generic symlink forest creation tool, we'd need to add it, too.
199 context.deps = append(context.deps, srcDir)
200
201 srcDirMap := readdirToMap(shared.JoinPath(context.topdir, srcDir))
202 buildFilesMap := readdirToMap(shared.JoinPath(context.topdir, buildFilesDir))
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200203
Cole Faust324a92e2022-08-23 15:29:05 -0700204 renamingBuildFile := false
205 if _, ok := srcDirMap["BUILD"]; ok {
206 if _, ok := srcDirMap["BUILD.bazel"]; !ok {
207 if _, ok := buildFilesMap["BUILD.bazel"]; ok {
208 renamingBuildFile = true
209 srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
210 delete(srcDirMap, "BUILD")
211 }
212 }
213 }
214
Usta Shrestha49d04e82022-10-17 17:36:49 -0400215 allEntries := make(map[string]struct{})
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400216 for n := range srcDirMap {
Usta Shrestha49d04e82022-10-17 17:36:49 -0400217 allEntries[n] = struct{}{}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200218 }
219
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400220 for n := range buildFilesMap {
Usta Shrestha49d04e82022-10-17 17:36:49 -0400221 allEntries[n] = struct{}{}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200222 }
223
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000224 err := os.MkdirAll(shared.JoinPath(context.topdir, forestDir), 0777)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200225 if err != nil {
226 fmt.Fprintf(os.Stderr, "Cannot mkdir '%s': %s\n", forestDir, err)
227 os.Exit(1)
228 }
229
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400230 for f := range allEntries {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200231 if f[0] == '.' {
232 continue // Ignore dotfiles
233 }
234
235 // The full paths of children in the input trees and in the output tree
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200236 forestChild := shared.JoinPath(forestDir, f)
237 srcChild := shared.JoinPath(srcDir, f)
Cole Faust324a92e2022-08-23 15:29:05 -0700238 if f == "BUILD.bazel" && renamingBuildFile {
239 srcChild = shared.JoinPath(srcDir, "BUILD")
240 }
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200241 buildFilesChild := shared.JoinPath(buildFilesDir, f)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200242
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000243 // Descend in the instruction tree if it exists
244 var instructionsChild *instructionsNode = nil
245 if instructions != nil {
Cole Faust324a92e2022-08-23 15:29:05 -0700246 if f == "BUILD.bazel" && renamingBuildFile {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000247 instructionsChild = instructions.children["BUILD"]
Cole Faust324a92e2022-08-23 15:29:05 -0700248 } else {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000249 instructionsChild = instructions.children[f]
Cole Faust324a92e2022-08-23 15:29:05 -0700250 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200251 }
252
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200253 srcChildEntry, sExists := srcDirMap[f]
254 buildFilesChildEntry, bExists := buildFilesMap[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200255
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000256 if instructionsChild != nil && instructionsChild.excluded {
Cole Faust324a92e2022-08-23 15:29:05 -0700257 if bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000258 symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
Cole Faust324a92e2022-08-23 15:29:05 -0700259 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200260 continue
261 }
262
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000263 sDir := sExists && isDir(shared.JoinPath(context.topdir, srcChild), srcChildEntry)
264 bDir := bExists && isDir(shared.JoinPath(context.topdir, buildFilesChild), buildFilesChildEntry)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200265
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200266 if !sExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000267 if bDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200268 // Not in the source tree, but we have to exclude something from under
269 // this subtree, so descend
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000270 plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200271 } else {
272 // Not in the source tree, symlink BUILD file
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000273 symlinkIntoForest(context.topdir, forestChild, buildFilesChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200274 }
275 } else if !bExists {
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000276 if sDir && instructionsChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200277 // Not in the build file tree, but we have to exclude something from
278 // under this subtree, so descend
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000279 plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200280 } else {
281 // Not in the build file tree, symlink source tree, carry on
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000282 symlinkIntoForest(context.topdir, forestChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200283 }
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200284 } else if sDir && bDir {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200285 // Both are directories. Descend.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000286 plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200287 } else if !sDir && !bDir {
Cole Faust324a92e2022-08-23 15:29:05 -0700288 // Neither is a directory. Merge them.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000289 srcBuildFile := shared.JoinPath(context.topdir, srcChild)
290 generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild)
Usta Shrestha783ec5c2022-10-25 01:22:36 -0400291 // The Android.bp file that codegen used to produce `buildFilesChild` is
292 // already a dependency, we can ignore `buildFilesChild`.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000293 context.deps = append(context.deps, srcChild)
294 err = mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose)
Cole Faust324a92e2022-08-23 15:29:05 -0700295 if err != nil {
296 fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
297 srcBuildFile, generatedBuildFile, err)
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000298 context.okay = false
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700299 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200300 } else {
301 // Both exist and one is a file. This is an error.
302 fmt.Fprintf(os.Stderr,
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200303 "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 +0200304 srcChild, buildFilesChild)
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000305 context.okay = false
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200306 }
307 }
308}
309
310// Creates a symlink forest by merging the directory tree at "buildFiles" and
311// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
312// under srcDir on which readdir() had to be called to produce the symlink
313// forest.
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000314func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) []string {
315 context := &symlinkForestContext{
316 verbose: verbose,
317 topdir: topdir,
318 deps: make([]string, 0),
319 okay: true,
320 }
321
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200322 os.RemoveAll(shared.JoinPath(topdir, forest))
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000323
324 instructions := instructionsFromExcludePathList(exclude)
325 plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".")
326 if !context.okay {
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200327 os.Exit(1)
328 }
Lukacs T. Berkic541cd22022-10-26 07:26:50 +0000329 return context.deps
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200330}