blob: 78e7b0e75763e15e32ea64c3956918d710c8e0ef [file] [log] [blame]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +02001package bp2build
2
3import (
Sasha Smundak0fd93e02022-05-19 19:34:31 -07004 "android/soong/android"
Lukacs T. Berkib353cca2021-04-16 13:47:36 +02005 "fmt"
6 "io/ioutil"
7 "os"
8 "path/filepath"
9
10 "android/soong/shared"
11)
12
13// A tree structure that describes what to do at each directory in the created
14// symlink tree. Currently it is used to enumerate which files/directories
15// should be excluded from symlinking. Each instance of "node" represents a file
16// or a directory. If excluded is true, then that file/directory should be
17// excluded from symlinking. Otherwise, the node is not excluded, but one of its
18// descendants is (otherwise the node in question would not exist)
19type node struct {
20 name string
21 excluded bool // If false, this is just an intermediate node
22 children map[string]*node
23}
24
Usta Shresthadb46a9b2022-07-11 11:29:56 -040025// Ensures that the node for the given path exists in the tree and returns it.
Lukacs T. Berkib353cca2021-04-16 13:47:36 +020026func ensureNodeExists(root *node, path string) *node {
27 if path == "" {
28 return root
29 }
30
31 if path[len(path)-1] == '/' {
32 path = path[:len(path)-1] // filepath.Split() leaves a trailing slash
33 }
34
35 dir, base := filepath.Split(path)
36
37 // First compute the parent node...
38 dn := ensureNodeExists(root, dir)
39
40 // then create the requested node as its direct child, if needed.
41 if child, ok := dn.children[base]; ok {
42 return child
43 } else {
44 dn.children[base] = &node{base, false, make(map[string]*node)}
45 return dn.children[base]
46 }
47}
48
49// Turns a list of paths to be excluded into a tree made of "node" objects where
50// the specified paths are marked as excluded.
51func treeFromExcludePathList(paths []string) *node {
52 result := &node{"", false, make(map[string]*node)}
53
54 for _, p := range paths {
55 ensureNodeExists(result, p).excluded = true
56 }
57
58 return result
59}
60
61// Calls readdir() and returns it as a map from the basename of the files in dir
62// to os.FileInfo.
63func readdirToMap(dir string) map[string]os.FileInfo {
64 entryList, err := ioutil.ReadDir(dir)
65 result := make(map[string]os.FileInfo)
66
67 if err != nil {
68 if os.IsNotExist(err) {
69 // It's okay if a directory doesn't exist; it just means that one of the
70 // trees to be merged contains parts the other doesn't
71 return result
72 } else {
73 fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err)
74 os.Exit(1)
75 }
76 }
77
78 for _, fi := range entryList {
79 result[fi.Name()] = fi
80 }
81
82 return result
83}
84
85// Creates a symbolic link at dst pointing to src
86func symlinkIntoForest(topdir, dst, src string) {
87 err := os.Symlink(shared.JoinPath(topdir, src), shared.JoinPath(topdir, dst))
88 if err != nil {
89 fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err)
90 os.Exit(1)
91 }
92}
93
Lukacs T. Berkie3487c82022-05-02 10:13:19 +020094func isDir(path string, fi os.FileInfo) bool {
95 if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink {
96 return fi.IsDir()
97 }
98
Jingwen Chend4b1dc82022-05-12 11:08:03 +000099 fi2, statErr := os.Stat(path)
100 if statErr == nil {
101 return fi2.IsDir()
102 }
103
104 // Check if this is a dangling symlink. If so, treat it like a file, not a dir.
105 _, lstatErr := os.Lstat(path)
106 if lstatErr != nil {
107 fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200108 os.Exit(1)
109 }
110
Jingwen Chend4b1dc82022-05-12 11:08:03 +0000111 return false
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200112}
113
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200114// Recursively plants a symlink forest at forestDir. The symlink tree will
115// contain every file in buildFilesDir and srcDir excluding the files in
116// exclude. Collects every directory encountered during the traversal of srcDir
117// into acc.
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700118func 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 +0200119 if exclude != nil && exclude.excluded {
120 // This directory is not needed, bail out
121 return
122 }
123
124 *acc = append(*acc, srcDir)
125 srcDirMap := readdirToMap(shared.JoinPath(topdir, srcDir))
126 buildFilesMap := readdirToMap(shared.JoinPath(topdir, buildFilesDir))
127
128 allEntries := make(map[string]bool)
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400129 for n := range srcDirMap {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200130 allEntries[n] = true
131 }
132
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400133 for n := range buildFilesMap {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200134 allEntries[n] = true
135 }
136
137 err := os.MkdirAll(shared.JoinPath(topdir, forestDir), 0777)
138 if err != nil {
139 fmt.Fprintf(os.Stderr, "Cannot mkdir '%s': %s\n", forestDir, err)
140 os.Exit(1)
141 }
142
Usta Shresthadb46a9b2022-07-11 11:29:56 -0400143 for f := range allEntries {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200144 if f[0] == '.' {
145 continue // Ignore dotfiles
146 }
147
148 // The full paths of children in the input trees and in the output tree
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200149 forestChild := shared.JoinPath(forestDir, f)
150 srcChild := shared.JoinPath(srcDir, f)
151 buildFilesChild := shared.JoinPath(buildFilesDir, f)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200152
153 // Descend in the exclusion tree, if there are any excludes left
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200154 var excludeChild *node
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200155 if exclude == nil {
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200156 excludeChild = nil
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200157 } else {
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200158 excludeChild = exclude.children[f]
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200159 }
160
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200161 srcChildEntry, sExists := srcDirMap[f]
162 buildFilesChildEntry, bExists := buildFilesMap[f]
163 excluded := excludeChild != nil && excludeChild.excluded
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200164
165 if excluded {
166 continue
167 }
168
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200169 sDir := false
170 bDir := false
171 if sExists {
172 sDir = isDir(shared.JoinPath(topdir, srcChild), srcChildEntry)
173 }
174
175 if bExists {
176 bDir = isDir(shared.JoinPath(topdir, buildFilesChild), buildFilesChildEntry)
177 }
178
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200179 if !sExists {
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200180 if bDir && excludeChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200181 // Not in the source tree, but we have to exclude something from under
182 // this subtree, so descend
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700183 plantSymlinkForestRecursive(cfg, topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200184 } else {
185 // Not in the source tree, symlink BUILD file
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200186 symlinkIntoForest(topdir, forestChild, buildFilesChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200187 }
188 } else if !bExists {
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200189 if sDir && excludeChild != nil {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200190 // Not in the build file tree, but we have to exclude something from
191 // under this subtree, so descend
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700192 plantSymlinkForestRecursive(cfg, topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200193 } else {
194 // Not in the build file tree, symlink source tree, carry on
Lukacs T. Berki3f9416e2021-04-20 13:08:11 +0200195 symlinkIntoForest(topdir, forestChild, srcChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200196 }
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200197 } else if sDir && bDir {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200198 // Both are directories. Descend.
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700199 plantSymlinkForestRecursive(cfg, topdir, forestChild, buildFilesChild, srcChild, excludeChild, acc, okay)
Lukacs T. Berkie3487c82022-05-02 10:13:19 +0200200 } else if !sDir && !bDir {
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200201 // Neither is a directory. Prioritize BUILD files generated by bp2build
202 // over any BUILD file imported into external/.
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700203 if cfg.IsEnvTrue("BP2BUILD_VERBOSE") {
204 fmt.Fprintf(os.Stderr, "Both '%s' and '%s' exist, symlinking the former to '%s'\n",
205 buildFilesChild, srcChild, forestChild)
206 }
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200207 symlinkIntoForest(topdir, forestChild, buildFilesChild)
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200208 } else {
209 // Both exist and one is a file. This is an error.
210 fmt.Fprintf(os.Stderr,
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200211 "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 +0200212 srcChild, buildFilesChild)
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200213 *okay = false
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200214 }
215 }
216}
217
218// Creates a symlink forest by merging the directory tree at "buildFiles" and
219// "srcDir" while excluding paths listed in "exclude". Returns the set of paths
220// under srcDir on which readdir() had to be called to produce the symlink
221// forest.
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700222func PlantSymlinkForest(cfg android.Config, topdir string, forest string, buildFiles string, srcDir string, exclude []string) []string {
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200223 deps := make([]string, 0)
224 os.RemoveAll(shared.JoinPath(topdir, forest))
225 excludeTree := treeFromExcludePathList(exclude)
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200226 okay := true
Sasha Smundak0fd93e02022-05-19 19:34:31 -0700227 plantSymlinkForestRecursive(cfg, topdir, forest, buildFiles, srcDir, excludeTree, &deps, &okay)
Lukacs T. Berkib21166e2021-04-21 11:58:36 +0200228 if !okay {
229 os.Exit(1)
230 }
Lukacs T. Berkib353cca2021-04-16 13:47:36 +0200231 return deps
232}