blob: 092b240399dce1c94a326c5a96c2400a69e5dc59 [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
Cole Faust324a92e2022-08-23 15:29:05 -070024 "android/soong/android"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020025 "android/soong/shared"
26)
27
28// A tree structure that describes what to do at each directory in the created
29// symlink tree. Currently it is used to enumerate which files/directories
30// should be excluded from symlinking. Each instance of "node" represents a file
31// or a directory. If excluded is true, then that file/directory should be
32// excluded from symlinking. Otherwise, the node is not excluded, but one of its
33// descendants is (otherwise the node in question would not exist)
34type node struct {
35 name string
36 excluded bool // If false, this is just an intermediate node
37 children map[string]*node
38}
39
Usta Shresthadb46a9b2022-07-11 11:29:56 -040040// Ensures that the node for the given path exists in the tree and returns it.
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020041func ensureNodeExists(root *node, path string) *node {
42 if path == "" {
43 return root
44 }
45
46 if path[len(path)-1] == '/' {
47 path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
48 }
49
50 dir, base := filepath.Split(path)
51
52 // First compute the parent node...
53 dn := ensureNodeExists(root, dir)
54
55 // then create the requested node as its direct child, if needed.
56 if child, ok := dn.children[base]; ok {
57 return child
58 } else {
59 dn.children[base] = &node{base, false, make(map[string]*node)}
60 return dn.children[base]
61 }
62}
63
64// Turns a list of paths to be excluded into a tree made of "node" objects where
65// the specified paths are marked as excluded.
66func treeFromExcludePathList(paths []string) *node {
67 result := &node{"", false, make(map[string]*node)}
68
69 for _, p := range paths {
70 ensureNodeExists(result, p).excluded = true
71 }
72
73 return result
74}
75
Cole Faust324a92e2022-08-23 15:29:05 -070076func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error {
77
78 srcBuildFileContent, err := os.ReadFile(srcBuildFile)
79 if err != nil {
80 return err
81 }
82
83 generatedBuildFileContent, err := os.ReadFile(generatedBuildFile)
84 if err != nil {
85 return err
86 }
87
88 // There can't be a package() call in both the source and generated BUILD files.
89 // bp2build will generate a package() call for licensing information, but if
90 // there's no licensing information, it will still generate a package() call
91 // that just sets default_visibility=public. If the handcrafted build file
92 // also has a package() call, we'll allow it to override the bp2build
93 // generated one if it doesn't have any licensing information. If the bp2build
94 // one has licensing information and the handcrafted one exists, we'll leave
95 // them both in for bazel to throw an error.
96 packageRegex := regexp.MustCompile(`(?m)^package\s*\(`)
97 packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`)
98 if packageRegex.Find(srcBuildFileContent) != nil {
99 if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil {
100 fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n",
101 generatedBuildFile, srcBuildFile)
102 }
103 generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{})
104 }
105
106 outFile, err := os.Create(output)
107 if err != nil {
108 return err
109 }
110
111 _, err = outFile.Write(generatedBuildFileContent)
112 if err != nil {
113 return err
114 }
115
116 if generatedBuildFileContent[len(generatedBuildFileContent)-1] != '\n' {
117 _, err = outFile.WriteString("\n")
118 if err != nil {
119 return err
120 }
121 }
122
123 _, err = outFile.Write(srcBuildFileContent)
124 return err
125}
126
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200127// Calls readdir() and returns it as a map from the basename of the files in dir
128// to os.FileInfo.
129func readdirToMap(dir string) map[string]os.FileInfo {
130 entryList, err := ioutil.ReadDir(dir)
131 result := make(map[string]os.FileInfo)
132
133 if err != nil {
134 if os.IsNotExist(err) {
135 // It's okay if a directory doesn't exist; it just means that one of the
136 // trees to be merged contains parts the other doesn't
137 return result
138 } else {
139 fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
140 os.Exit(1)
141 }
142 }
143
144 for _, fi := range entryList {
145 result[fi.Name()] = fi
146 }
147
148 return result
149}
150
151// Creates a symbolic link at dst pointing to src
152func symlinkIntoForest(topdir, dst, src string) {
153 err := os.Symlink(shared.JoinPath(topdir, src), shared.JoinPath(topdir, dst))
154 if err != nil {
155 fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
156 os.Exit(1)
157 }
158}
159
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200160func isDir(path string, fi os.FileInfo) bool {
161 if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
162 return fi.IsDir()
163 }
164
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000165 fi2, statErr := os.Stat(path)
166 if statErr == nil {
167 return fi2.IsDir()
168 }
169
170 // Check if this is a dangling symlink. If so, treat it like a file, not a dir.
171 _, lstatErr := os.Lstat(path)
172 if lstatErr != nil {
173 fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200174 os.Exit(1)
175 }
176
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000177 return false
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200178}
179
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200180// Recursively plants a symlink forest at forestDir. The symlink tree will
181// contain every file in buildFilesDir and srcDir excluding the files in
182// exclude. Collects every directory encountered during the traversal of srcDir
183// into acc.
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700184func plantSymlinkForestRecursive(cfg android.Config, topdir string, forestDir string, buildFilesDir string, srcDir string, exclude *node, acc *[]string, okay *bool) {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200185 if exclude != nil && exclude.excluded {
186 // This directory is not needed, bail out
187 return
188 }
189
190 *acc = append(*acc, srcDir)
191 srcDirMap := readdirToMap(shared.JoinPath(topdir, srcDir))
192 buildFilesMap := readdirToMap(shared.JoinPath(topdir, buildFilesDir))
193
Cole Faust324a92e2022-08-23 15:29:05 -0700194 renamingBuildFile := false
195 if _, ok := srcDirMap["BUILD"]; ok {
196 if _, ok := srcDirMap["BUILD.bazel"]; !ok {
197 if _, ok := buildFilesMap["BUILD.bazel"]; ok {
198 renamingBuildFile = true
199 srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"]
200 delete(srcDirMap, "BUILD")
201 }
202 }
203 }
204
Usta Shrestha49d04e82022-10-17 17:36:49 -0400205 allEntries := make(map[string]struct{})
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400206 for n := range srcDirMap {
Usta Shrestha49d04e82022-10-17 17:36:49 -0400207 allEntries[n] = struct{}{}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200208 }
209
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400210 for n := range buildFilesMap {
Usta Shrestha49d04e82022-10-17 17:36:49 -0400211 allEntries[n] = struct{}{}
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200212 }
213
214 err := os.MkdirAll(shared.JoinPath(topdir, forestDir), 0777)
215 if err != nil {
216 fmt.Fprintf(os.Stderr, "Cannot mkdir '%s': %s\n", forestDir, err)
217 os.Exit(1)
218 }
219
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400220 for f := range allEntries {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200221 if f[0] == '.' {
222 continue // Ignore dotfiles
223 }
224
225 // The full paths of children in the input trees and in the output tree
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200226 forestChild := shared.JoinPath(forestDir, f)
227 srcChild := shared.JoinPath(srcDir, f)
Cole Faust324a92e2022-08-23 15:29:05 -0700228 if f == "BUILD.bazel" && renamingBuildFile {
229 srcChild = shared.JoinPath(srcDir, "BUILD")
230 }
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200231 buildFilesChild := shared.JoinPath(buildFilesDir, f)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200232
233 // Descend in the exclusion tree, if there are any excludes left
Cole Faust324a92e2022-08-23 15:29:05 -0700234 var excludeChild *node = nil
235 if exclude != nil {
236 if f == "BUILD.bazel" && renamingBuildFile {
237 excludeChild = exclude.children["BUILD"]
238 } else {
239 excludeChild = exclude.children[f]
240 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200241 }
242
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200243 srcChildEntry, sExists := srcDirMap[f]
244 buildFilesChildEntry, bExists := buildFilesMap[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200245
Cole Faust324a92e2022-08-23 15:29:05 -0700246 if excludeChild != nil && excludeChild.excluded {
247 if bExists {
248 symlinkIntoForest(topdir, forestChild, buildFilesChild)
249 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200250 continue
251 }
252
Usta Shrestha49d04e82022-10-17 17:36:49 -0400253 sDir := sExists && isDir(shared.JoinPath(topdir, srcChild), srcChildEntry)
254 bDir := bExists && isDir(shared.JoinPath(topdir, buildFilesChild), buildFilesChildEntry)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200255
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200256 if !sExists {
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200257 if bDir && excludeChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200258 // Not in the source tree, but we have to exclude something from under
259 // this subtree, so descend
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700260 plantSymlinkForestRecursive(cfg, topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200261 } else {
262 // Not in the source tree, symlink BUILD file
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200263 symlinkIntoForest(topdir, forestChild, buildFilesChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200264 }
265 } else if !bExists {
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200266 if sDir && excludeChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200267 // Not in the build file tree, but we have to exclude something from
268 // under this subtree, so descend
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700269 plantSymlinkForestRecursive(cfg, topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200270 } else {
271 // Not in the build file tree, symlink source tree, carry on
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200272 symlinkIntoForest(topdir, forestChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200273 }
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200274 } else if sDir && bDir {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200275 // Both are directories. Descend.
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700276 plantSymlinkForestRecursive(cfg, topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200277 } else if !sDir && !bDir {
Cole Faust324a92e2022-08-23 15:29:05 -0700278 // Neither is a directory. Merge them.
279 srcBuildFile := shared.JoinPath(topdir, srcChild)
280 generatedBuildFile := shared.JoinPath(topdir, buildFilesChild)
Usta Shrestha783ec5c2022-10-25 01:22:36 -0400281 // The Android.bp file that codegen used to produce `buildFilesChild` is
282 // already a dependency, we can ignore `buildFilesChild`.
283 *acc = append(*acc, srcChild)
Cole Faust324a92e2022-08-23 15:29:05 -0700284 err = mergeBuildFiles(shared.JoinPath(topdir, forestChild), srcBuildFile, generatedBuildFile, cfg.IsEnvTrue("BP2BUILD_VERBOSE"))
285 if err != nil {
286 fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s",
287 srcBuildFile, generatedBuildFile, err)
288 *okay = false
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700289 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200290 } else {
291 // Both exist and one is a file. This is an error.
292 fmt.Fprintf(os.Stderr,
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200293 "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 +0200294 srcChild, buildFilesChild)
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200295 *okay = false
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200296 }
297 }
298}
299
300// Creates a symlink forest by merging the directory tree at "buildFiles" and
301// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
302// under srcDir on which readdir() had to be called to produce the symlink
303// forest.
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700304func PlantSymlinkForest(cfg android.Config, topdir string, forest string, buildFiles string, srcDir string, exclude []string) []string {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200305 deps := make([]string, 0)
306 os.RemoveAll(shared.JoinPath(topdir, forest))
307 excludeTree := treeFromExcludePathList(exclude)
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200308 okay := true
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700309 plantSymlinkForestRecursive(cfg, topdir, forest, buildFiles, srcDir, excludeTree, &deps, &okay)
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200310 if !okay {
311 os.Exit(1)
312 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200313 return deps
314}