| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 1 | // 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. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 15 | package bp2build | 
 | 16 |  | 
 | 17 | import ( | 
 | 18 | 	"fmt" | 
 | 19 | 	"io/ioutil" | 
 | 20 | 	"os" | 
 | 21 | 	"path/filepath" | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 22 | 	"regexp" | 
| Cole Faust | 358ba4f | 2023-01-18 15:00:42 -0800 | [diff] [blame] | 23 | 	"sort" | 
| Cole Faust | 5db9209 | 2023-04-05 11:36:23 -0700 | [diff] [blame] | 24 | 	"strconv" | 
| Lukacs T. Berki | 647e7ab | 2022-10-27 09:55:38 +0000 | [diff] [blame] | 25 | 	"sync" | 
 | 26 | 	"sync/atomic" | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 27 |  | 
 | 28 | 	"android/soong/shared" | 
| Cole Faust | 3f4f521 | 2023-04-04 15:12:22 -0700 | [diff] [blame] | 29 |  | 
| Chris Parsons | 1a12d03 | 2023-02-06 22:37:41 -0500 | [diff] [blame] | 30 | 	"github.com/google/blueprint/pathtools" | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 31 | ) | 
 | 32 |  | 
 | 33 | // A tree structure that describes what to do at each directory in the created | 
| Mark Dacek | b2441c5 | 2023-10-04 16:12:00 +0000 | [diff] [blame] | 34 | // symlink tree. Currently, it is used to enumerate which files/directories | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 35 | // should be excluded from symlinking. Each instance of "node" represents a file | 
 | 36 | // or a directory. If excluded is true, then that file/directory should be | 
 | 37 | // excluded from symlinking. Otherwise, the node is not excluded, but one of its | 
 | 38 | // descendants is (otherwise the node in question would not exist) | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 39 |  | 
 | 40 | type instructionsNode struct { | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 41 | 	name     string | 
 | 42 | 	excluded bool // If false, this is just an intermediate node | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 43 | 	children map[string]*instructionsNode | 
 | 44 | } | 
 | 45 |  | 
 | 46 | type symlinkForestContext struct { | 
 | 47 | 	verbose bool | 
| Lukacs T. Berki | 647e7ab | 2022-10-27 09:55:38 +0000 | [diff] [blame] | 48 | 	topdir  string // $TOPDIR | 
 | 49 |  | 
 | 50 | 	// State | 
| Usta Shrestha | da15c61 | 2022-11-08 14:12:36 -0500 | [diff] [blame] | 51 | 	wg           sync.WaitGroup | 
 | 52 | 	depCh        chan string | 
 | 53 | 	mkdirCount   atomic.Uint64 | 
 | 54 | 	symlinkCount atomic.Uint64 | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 55 | } | 
 | 56 |  | 
| Usta Shrestha | db46a9b | 2022-07-11 11:29:56 -0400 | [diff] [blame] | 57 | // Ensures that the node for the given path exists in the tree and returns it. | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 58 | func ensureNodeExists(root *instructionsNode, path string) *instructionsNode { | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 59 | 	if path == "" { | 
 | 60 | 		return root | 
 | 61 | 	} | 
 | 62 |  | 
 | 63 | 	if path[len(path)-1] == '/' { | 
 | 64 | 		path = path[:len(path)-1] // filepath.Split() leaves a trailing slash | 
 | 65 | 	} | 
 | 66 |  | 
 | 67 | 	dir, base := filepath.Split(path) | 
 | 68 |  | 
 | 69 | 	// First compute the parent node... | 
 | 70 | 	dn := ensureNodeExists(root, dir) | 
 | 71 |  | 
 | 72 | 	// then create the requested node as its direct child, if needed. | 
 | 73 | 	if child, ok := dn.children[base]; ok { | 
 | 74 | 		return child | 
 | 75 | 	} else { | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 76 | 		dn.children[base] = &instructionsNode{base, false, make(map[string]*instructionsNode)} | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 77 | 		return dn.children[base] | 
 | 78 | 	} | 
 | 79 | } | 
 | 80 |  | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 81 | // Turns a list of paths to be excluded into a tree | 
 | 82 | func instructionsFromExcludePathList(paths []string) *instructionsNode { | 
 | 83 | 	result := &instructionsNode{"", false, make(map[string]*instructionsNode)} | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 84 |  | 
 | 85 | 	for _, p := range paths { | 
 | 86 | 		ensureNodeExists(result, p).excluded = true | 
 | 87 | 	} | 
 | 88 |  | 
 | 89 | 	return result | 
 | 90 | } | 
 | 91 |  | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 92 | func mergeBuildFiles(output string, srcBuildFile string, generatedBuildFile string, verbose bool) error { | 
 | 93 |  | 
 | 94 | 	srcBuildFileContent, err := os.ReadFile(srcBuildFile) | 
 | 95 | 	if err != nil { | 
 | 96 | 		return err | 
 | 97 | 	} | 
 | 98 |  | 
 | 99 | 	generatedBuildFileContent, err := os.ReadFile(generatedBuildFile) | 
 | 100 | 	if err != nil { | 
 | 101 | 		return err | 
 | 102 | 	} | 
 | 103 |  | 
 | 104 | 	// There can't be a package() call in both the source and generated BUILD files. | 
 | 105 | 	// bp2build will generate a package() call for licensing information, but if | 
 | 106 | 	// there's no licensing information, it will still generate a package() call | 
 | 107 | 	// that just sets default_visibility=public. If the handcrafted build file | 
 | 108 | 	// also has a package() call, we'll allow it to override the bp2build | 
 | 109 | 	// generated one if it doesn't have any licensing information. If the bp2build | 
 | 110 | 	// one has licensing information and the handcrafted one exists, we'll leave | 
 | 111 | 	// them both in for bazel to throw an error. | 
 | 112 | 	packageRegex := regexp.MustCompile(`(?m)^package\s*\(`) | 
 | 113 | 	packageDefaultVisibilityRegex := regexp.MustCompile(`(?m)^package\s*\(\s*default_visibility\s*=\s*\[\s*"//visibility:public",?\s*]\s*\)`) | 
 | 114 | 	if packageRegex.Find(srcBuildFileContent) != nil { | 
 | 115 | 		if verbose && packageDefaultVisibilityRegex.Find(generatedBuildFileContent) != nil { | 
 | 116 | 			fmt.Fprintf(os.Stderr, "Both '%s' and '%s' have a package() target, removing the first one\n", | 
 | 117 | 				generatedBuildFile, srcBuildFile) | 
 | 118 | 		} | 
 | 119 | 		generatedBuildFileContent = packageDefaultVisibilityRegex.ReplaceAll(generatedBuildFileContent, []byte{}) | 
 | 120 | 	} | 
 | 121 |  | 
| Chris Parsons | 1a12d03 | 2023-02-06 22:37:41 -0500 | [diff] [blame] | 122 | 	newContents := generatedBuildFileContent | 
 | 123 | 	if newContents[len(newContents)-1] != '\n' { | 
 | 124 | 		newContents = append(newContents, '\n') | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 125 | 	} | 
| Chris Parsons | 1a12d03 | 2023-02-06 22:37:41 -0500 | [diff] [blame] | 126 | 	newContents = append(newContents, srcBuildFileContent...) | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 127 |  | 
| Cole Faust | 3f4f521 | 2023-04-04 15:12:22 -0700 | [diff] [blame] | 128 | 	// Say you run bp2build 4 times: | 
 | 129 | 	// - The first time there's only an Android.bp file. bp2build will convert it to a build file | 
 | 130 | 	//   under out/soong/bp2build, then symlink from the forest to that generated file | 
 | 131 | 	// - Then you add a handcrafted BUILD file in the same directory. bp2build will merge this with | 
 | 132 | 	//   the generated one, and write the result to the output file in the forest. But the output | 
 | 133 | 	//   file was a symlink to out/soong/bp2build from the previous step! So we erroneously update | 
 | 134 | 	//   the file in out/soong/bp2build instead. So far this doesn't cause any problems... | 
 | 135 | 	// - You run a 3rd bp2build with no relevant changes. Everything continues to work. | 
 | 136 | 	// - You then add a comment to the handcrafted BUILD file. This causes a merge with the | 
 | 137 | 	//   generated file again. But since we wrote to the generated file in step 2, the generated | 
 | 138 | 	//   file has an old copy of the handcrafted file in it! This probably causes duplicate bazel | 
 | 139 | 	//   targets. | 
 | 140 | 	// To solve this, if we see that the output file is a symlink from a previous build, remove it. | 
 | 141 | 	stat, err := os.Lstat(output) | 
 | 142 | 	if err != nil && !os.IsNotExist(err) { | 
 | 143 | 		return err | 
 | 144 | 	} else if err == nil { | 
 | 145 | 		if stat.Mode()&os.ModeSymlink == os.ModeSymlink { | 
 | 146 | 			if verbose { | 
 | 147 | 				fmt.Fprintf(os.Stderr, "Removing symlink so that we can replace it with a merged file: %s\n", output) | 
 | 148 | 			} | 
 | 149 | 			err = os.Remove(output) | 
 | 150 | 			if err != nil { | 
 | 151 | 				return err | 
 | 152 | 			} | 
 | 153 | 		} | 
 | 154 | 	} | 
 | 155 |  | 
| Chris Parsons | 1a12d03 | 2023-02-06 22:37:41 -0500 | [diff] [blame] | 156 | 	return pathtools.WriteFileIfChanged(output, newContents, 0666) | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 157 | } | 
 | 158 |  | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 159 | // Calls readdir() and returns it as a map from the basename of the files in dir | 
 | 160 | // to os.FileInfo. | 
 | 161 | func readdirToMap(dir string) map[string]os.FileInfo { | 
 | 162 | 	entryList, err := ioutil.ReadDir(dir) | 
 | 163 | 	result := make(map[string]os.FileInfo) | 
 | 164 |  | 
 | 165 | 	if err != nil { | 
 | 166 | 		if os.IsNotExist(err) { | 
 | 167 | 			// It's okay if a directory doesn't exist; it just means that one of the | 
 | 168 | 			// trees to be merged contains parts the other doesn't | 
 | 169 | 			return result | 
 | 170 | 		} else { | 
 | 171 | 			fmt.Fprintf(os.Stderr, "Cannot readdir '%s': %s\n", dir, err) | 
 | 172 | 			os.Exit(1) | 
 | 173 | 		} | 
 | 174 | 	} | 
 | 175 |  | 
 | 176 | 	for _, fi := range entryList { | 
 | 177 | 		result[fi.Name()] = fi | 
 | 178 | 	} | 
 | 179 |  | 
 | 180 | 	return result | 
 | 181 | } | 
 | 182 |  | 
 | 183 | // Creates a symbolic link at dst pointing to src | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 184 | func symlinkIntoForest(topdir, dst, src string) uint64 { | 
| Sebastian Pickl | 90355f7 | 2023-09-12 18:03:01 +0000 | [diff] [blame] | 185 | 	srcPath := shared.JoinPath(topdir, src) | 
 | 186 | 	dstPath := shared.JoinPath(topdir, dst) | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 187 |  | 
| Mark Dacek | b2441c5 | 2023-10-04 16:12:00 +0000 | [diff] [blame] | 188 | 	// Check whether a symlink already exists. | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 189 | 	if dstInfo, err := os.Lstat(dstPath); err != nil { | 
 | 190 | 		if !os.IsNotExist(err) { | 
 | 191 | 			fmt.Fprintf(os.Stderr, "Failed to lstat '%s': %s", dst, err) | 
 | 192 | 			os.Exit(1) | 
 | 193 | 		} | 
 | 194 | 	} else { | 
 | 195 | 		if dstInfo.Mode()&os.ModeSymlink != 0 { | 
 | 196 | 			// Assume that the link's target is correct, i.e. no manual tampering. | 
 | 197 | 			// E.g. OUT_DIR could have been previously used with a different source tree check-out! | 
 | 198 | 			return 0 | 
 | 199 | 		} else { | 
 | 200 | 			if err := os.RemoveAll(dstPath); err != nil { | 
 | 201 | 				fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", dst, err) | 
 | 202 | 				os.Exit(1) | 
 | 203 | 			} | 
 | 204 | 		} | 
 | 205 | 	} | 
 | 206 |  | 
 | 207 | 	// Create symlink. | 
 | 208 | 	if err := os.Symlink(srcPath, dstPath); err != nil { | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 209 | 		fmt.Fprintf(os.Stderr, "Cannot create symlink at '%s' pointing to '%s': %s", dst, src, err) | 
 | 210 | 		os.Exit(1) | 
 | 211 | 	} | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 212 | 	return 1 | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 213 | } | 
 | 214 |  | 
| Lukacs T. Berki | e3487c8 | 2022-05-02 10:13:19 +0200 | [diff] [blame] | 215 | func isDir(path string, fi os.FileInfo) bool { | 
 | 216 | 	if (fi.Mode() & os.ModeSymlink) != os.ModeSymlink { | 
 | 217 | 		return fi.IsDir() | 
 | 218 | 	} | 
 | 219 |  | 
| Jingwen Chen | d4b1dc8 | 2022-05-12 11:08:03 +0000 | [diff] [blame] | 220 | 	fi2, statErr := os.Stat(path) | 
 | 221 | 	if statErr == nil { | 
 | 222 | 		return fi2.IsDir() | 
 | 223 | 	} | 
 | 224 |  | 
 | 225 | 	// Check if this is a dangling symlink. If so, treat it like a file, not a dir. | 
 | 226 | 	_, lstatErr := os.Lstat(path) | 
 | 227 | 	if lstatErr != nil { | 
 | 228 | 		fmt.Fprintf(os.Stderr, "Cannot stat or lstat '%s': %s\n%s\n", path, statErr, lstatErr) | 
| Lukacs T. Berki | e3487c8 | 2022-05-02 10:13:19 +0200 | [diff] [blame] | 229 | 		os.Exit(1) | 
 | 230 | 	} | 
 | 231 |  | 
| Jingwen Chen | d4b1dc8 | 2022-05-12 11:08:03 +0000 | [diff] [blame] | 232 | 	return false | 
| Lukacs T. Berki | e3487c8 | 2022-05-02 10:13:19 +0200 | [diff] [blame] | 233 | } | 
 | 234 |  | 
| Mark Dacek | b2441c5 | 2023-10-04 16:12:00 +0000 | [diff] [blame] | 235 | // Returns the mtime of the soong_build binary to determine whether we should | 
 | 236 | // force symlink_forest to re-execute | 
 | 237 | func getSoongBuildMTime() (int64, error) { | 
 | 238 | 	binaryPath, err := os.Executable() | 
 | 239 | 	if err != nil { | 
 | 240 | 		return 0, err | 
| Cole Faust | 5db9209 | 2023-04-05 11:36:23 -0700 | [diff] [blame] | 241 | 	} | 
| Mark Dacek | b2441c5 | 2023-10-04 16:12:00 +0000 | [diff] [blame] | 242 |  | 
 | 243 | 	info, err := os.Stat(binaryPath) | 
 | 244 | 	if err != nil { | 
 | 245 | 		return 0, err | 
| Cole Faust | 5db9209 | 2023-04-05 11:36:23 -0700 | [diff] [blame] | 246 | 	} | 
| Mark Dacek | b2441c5 | 2023-10-04 16:12:00 +0000 | [diff] [blame] | 247 |  | 
 | 248 | 	return info.ModTime().UnixMilli(), nil | 
| Cole Faust | 5db9209 | 2023-04-05 11:36:23 -0700 | [diff] [blame] | 249 | } | 
 | 250 |  | 
| Mark Dacek | b2441c5 | 2023-10-04 16:12:00 +0000 | [diff] [blame] | 251 | // cleanSymlinkForest will remove the whole symlink forest directory | 
 | 252 | func cleanSymlinkForest(topdir, forest string) error { | 
 | 253 | 	return os.RemoveAll(shared.JoinPath(topdir, forest)) | 
 | 254 | } | 
 | 255 |  | 
 | 256 | // This returns whether symlink forest should clean and replant symlinks. | 
 | 257 | // It compares the mtime of this executable with the mtime of the last-run | 
 | 258 | // soong_build binary. If they differ, then we should clean and replant. | 
 | 259 | func shouldCleanSymlinkForest(topdir string, forest string, soongBuildMTime int64) (bool, error) { | 
 | 260 | 	mtimeFilePath := shared.JoinPath(topdir, forest, "soong_build_mtime") | 
 | 261 | 	mtimeFileContents, err := os.ReadFile(mtimeFilePath) | 
| Mark Dacek | aa5cc2c | 2023-10-02 23:40:33 +0000 | [diff] [blame] | 262 | 	if err != nil { | 
| Mark Dacek | b2441c5 | 2023-10-04 16:12:00 +0000 | [diff] [blame] | 263 | 		if os.IsNotExist(err) { | 
 | 264 | 			// This is likely the first time this has run with this functionality - clean away! | 
 | 265 | 			return true, nil | 
 | 266 | 		} else { | 
 | 267 | 			return false, err | 
| Mark Dacek | aa5cc2c | 2023-10-02 23:40:33 +0000 | [diff] [blame] | 268 | 		} | 
 | 269 | 	} | 
| Mark Dacek | b2441c5 | 2023-10-04 16:12:00 +0000 | [diff] [blame] | 270 | 	return strconv.FormatInt(soongBuildMTime, 10) != string(mtimeFileContents), nil | 
 | 271 | } | 
 | 272 |  | 
 | 273 | func writeSoongBuildMTimeFile(topdir, forest string, mtime int64) error { | 
 | 274 | 	mtimeFilePath := shared.JoinPath(topdir, forest, "soong_build_mtime") | 
 | 275 | 	contents := []byte(strconv.FormatInt(mtime, 10)) | 
 | 276 |  | 
 | 277 | 	return os.WriteFile(mtimeFilePath, contents, 0666) | 
| Cole Faust | 5db9209 | 2023-04-05 11:36:23 -0700 | [diff] [blame] | 278 | } | 
 | 279 |  | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 280 | // Recursively plants a symlink forest at forestDir. The symlink tree will | 
 | 281 | // contain every file in buildFilesDir and srcDir excluding the files in | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 282 | // instructions. Collects every directory encountered during the traversal of | 
 | 283 | // srcDir . | 
 | 284 | func plantSymlinkForestRecursive(context *symlinkForestContext, instructions *instructionsNode, forestDir string, buildFilesDir string, srcDir string) { | 
| Lukacs T. Berki | 647e7ab | 2022-10-27 09:55:38 +0000 | [diff] [blame] | 285 | 	defer context.wg.Done() | 
 | 286 |  | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 287 | 	if instructions != nil && instructions.excluded { | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 288 | 		// Excluded paths are skipped at the level of the non-excluded parent. | 
 | 289 | 		fmt.Fprintf(os.Stderr, "may not specify a root-level exclude directory '%s'", srcDir) | 
 | 290 | 		os.Exit(1) | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 291 | 	} | 
 | 292 |  | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 293 | 	// We don't add buildFilesDir here because the bp2build files marker files is | 
 | 294 | 	// already a dependency which covers it. If we ever wanted to turn this into | 
 | 295 | 	// a generic symlink forest creation tool, we'd need to add it, too. | 
| Lukacs T. Berki | 647e7ab | 2022-10-27 09:55:38 +0000 | [diff] [blame] | 296 | 	context.depCh <- srcDir | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 297 |  | 
 | 298 | 	srcDirMap := readdirToMap(shared.JoinPath(context.topdir, srcDir)) | 
 | 299 | 	buildFilesMap := readdirToMap(shared.JoinPath(context.topdir, buildFilesDir)) | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 300 |  | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 301 | 	renamingBuildFile := false | 
 | 302 | 	if _, ok := srcDirMap["BUILD"]; ok { | 
 | 303 | 		if _, ok := srcDirMap["BUILD.bazel"]; !ok { | 
 | 304 | 			if _, ok := buildFilesMap["BUILD.bazel"]; ok { | 
 | 305 | 				renamingBuildFile = true | 
 | 306 | 				srcDirMap["BUILD.bazel"] = srcDirMap["BUILD"] | 
 | 307 | 				delete(srcDirMap, "BUILD") | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 308 | 				if instructions != nil { | 
 | 309 | 					if _, ok := instructions.children["BUILD"]; ok { | 
 | 310 | 						instructions.children["BUILD.bazel"] = instructions.children["BUILD"] | 
 | 311 | 						delete(instructions.children, "BUILD") | 
 | 312 | 					} | 
 | 313 | 				} | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 314 | 			} | 
 | 315 | 		} | 
 | 316 | 	} | 
 | 317 |  | 
| Cole Faust | 358ba4f | 2023-01-18 15:00:42 -0800 | [diff] [blame] | 318 | 	allEntries := make([]string, 0, len(srcDirMap)+len(buildFilesMap)) | 
| Usta Shrestha | db46a9b | 2022-07-11 11:29:56 -0400 | [diff] [blame] | 319 | 	for n := range srcDirMap { | 
| Cole Faust | 358ba4f | 2023-01-18 15:00:42 -0800 | [diff] [blame] | 320 | 		allEntries = append(allEntries, n) | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 321 | 	} | 
| Usta Shrestha | db46a9b | 2022-07-11 11:29:56 -0400 | [diff] [blame] | 322 | 	for n := range buildFilesMap { | 
| Cole Faust | 358ba4f | 2023-01-18 15:00:42 -0800 | [diff] [blame] | 323 | 		if _, ok := srcDirMap[n]; !ok { | 
 | 324 | 			allEntries = append(allEntries, n) | 
 | 325 | 		} | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 326 | 	} | 
| Cole Faust | 358ba4f | 2023-01-18 15:00:42 -0800 | [diff] [blame] | 327 | 	// Tests read the error messages generated, so ensure their order is deterministic | 
 | 328 | 	sort.Strings(allEntries) | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 329 |  | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 330 | 	fullForestPath := shared.JoinPath(context.topdir, forestDir) | 
 | 331 | 	createForestDir := false | 
 | 332 | 	if fi, err := os.Lstat(fullForestPath); err != nil { | 
 | 333 | 		if os.IsNotExist(err) { | 
 | 334 | 			createForestDir = true | 
 | 335 | 		} else { | 
 | 336 | 			fmt.Fprintf(os.Stderr, "Could not read info for '%s': %s\n", forestDir, err) | 
 | 337 | 		} | 
 | 338 | 	} else if fi.Mode()&os.ModeDir == 0 { | 
 | 339 | 		if err := os.RemoveAll(fullForestPath); err != nil { | 
 | 340 | 			fmt.Fprintf(os.Stderr, "Failed to remove '%s': %s", forestDir, err) | 
 | 341 | 			os.Exit(1) | 
 | 342 | 		} | 
 | 343 | 		createForestDir = true | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 344 | 	} | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 345 | 	if createForestDir { | 
 | 346 | 		if err := os.MkdirAll(fullForestPath, 0777); err != nil { | 
 | 347 | 			fmt.Fprintf(os.Stderr, "Could not mkdir '%s': %s\n", forestDir, err) | 
 | 348 | 			os.Exit(1) | 
 | 349 | 		} | 
 | 350 | 		context.mkdirCount.Add(1) | 
 | 351 | 	} | 
 | 352 |  | 
 | 353 | 	// Start with a list of items that already exist in the forest, and remove | 
 | 354 | 	// each element as it is processed in allEntries. Any remaining items in | 
 | 355 | 	// forestMapForDeletion must be removed. (This handles files which were | 
 | 356 | 	// removed since the previous forest generation). | 
 | 357 | 	forestMapForDeletion := readdirToMap(shared.JoinPath(context.topdir, forestDir)) | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 358 |  | 
| Cole Faust | 358ba4f | 2023-01-18 15:00:42 -0800 | [diff] [blame] | 359 | 	for _, f := range allEntries { | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 360 | 		if f[0] == '.' { | 
 | 361 | 			continue // Ignore dotfiles | 
 | 362 | 		} | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 363 | 		delete(forestMapForDeletion, f) | 
 | 364 | 		// todo add deletionCount metric | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 365 |  | 
 | 366 | 		// The full paths of children in the input trees and in the output tree | 
| Lukacs T. Berki | 3f9416e | 2021-04-20 13:08:11 +0200 | [diff] [blame] | 367 | 		forestChild := shared.JoinPath(forestDir, f) | 
 | 368 | 		srcChild := shared.JoinPath(srcDir, f) | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 369 | 		if f == "BUILD.bazel" && renamingBuildFile { | 
 | 370 | 			srcChild = shared.JoinPath(srcDir, "BUILD") | 
 | 371 | 		} | 
| Lukacs T. Berki | 3f9416e | 2021-04-20 13:08:11 +0200 | [diff] [blame] | 372 | 		buildFilesChild := shared.JoinPath(buildFilesDir, f) | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 373 |  | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 374 | 		// Descend in the instruction tree if it exists | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 375 | 		var instructionsChild *instructionsNode | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 376 | 		if instructions != nil { | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 377 | 			instructionsChild = instructions.children[f] | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 378 | 		} | 
 | 379 |  | 
| Lukacs T. Berki | 3f9416e | 2021-04-20 13:08:11 +0200 | [diff] [blame] | 380 | 		srcChildEntry, sExists := srcDirMap[f] | 
 | 381 | 		buildFilesChildEntry, bExists := buildFilesMap[f] | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 382 |  | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 383 | 		if instructionsChild != nil && instructionsChild.excluded { | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 384 | 			if bExists { | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 385 | 				context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild)) | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 386 | 			} | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 387 | 			continue | 
 | 388 | 		} | 
 | 389 |  | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 390 | 		sDir := sExists && isDir(shared.JoinPath(context.topdir, srcChild), srcChildEntry) | 
 | 391 | 		bDir := bExists && isDir(shared.JoinPath(context.topdir, buildFilesChild), buildFilesChildEntry) | 
| Lukacs T. Berki | e3487c8 | 2022-05-02 10:13:19 +0200 | [diff] [blame] | 392 |  | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 393 | 		if !sExists { | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 394 | 			if bDir && instructionsChild != nil { | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 395 | 				// Not in the source tree, but we have to exclude something from under | 
 | 396 | 				// this subtree, so descend | 
| Lukacs T. Berki | 647e7ab | 2022-10-27 09:55:38 +0000 | [diff] [blame] | 397 | 				context.wg.Add(1) | 
 | 398 | 				go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild) | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 399 | 			} else { | 
 | 400 | 				// Not in the source tree, symlink BUILD file | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 401 | 				context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, buildFilesChild)) | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 402 | 			} | 
 | 403 | 		} else if !bExists { | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 404 | 			if sDir && instructionsChild != nil { | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 405 | 				// Not in the build file tree, but we have to exclude something from | 
 | 406 | 				// under this subtree, so descend | 
| Lukacs T. Berki | 647e7ab | 2022-10-27 09:55:38 +0000 | [diff] [blame] | 407 | 				context.wg.Add(1) | 
 | 408 | 				go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild) | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 409 | 			} else { | 
 | 410 | 				// Not in the build file tree, symlink source tree, carry on | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 411 | 				context.symlinkCount.Add(symlinkIntoForest(context.topdir, forestChild, srcChild)) | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 412 | 			} | 
| Lukacs T. Berki | e3487c8 | 2022-05-02 10:13:19 +0200 | [diff] [blame] | 413 | 		} else if sDir && bDir { | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 414 | 			// Both are directories. Descend. | 
| Lukacs T. Berki | 647e7ab | 2022-10-27 09:55:38 +0000 | [diff] [blame] | 415 | 			context.wg.Add(1) | 
 | 416 | 			go plantSymlinkForestRecursive(context, instructionsChild, forestChild, buildFilesChild, srcChild) | 
| Lukacs T. Berki | e3487c8 | 2022-05-02 10:13:19 +0200 | [diff] [blame] | 417 | 		} else if !sDir && !bDir { | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 418 | 			// Neither is a directory. Merge them. | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 419 | 			srcBuildFile := shared.JoinPath(context.topdir, srcChild) | 
 | 420 | 			generatedBuildFile := shared.JoinPath(context.topdir, buildFilesChild) | 
| Usta Shrestha | 783ec5c | 2022-10-25 01:22:36 -0400 | [diff] [blame] | 421 | 			// The Android.bp file that codegen used to produce `buildFilesChild` is | 
 | 422 | 			// already a dependency, we can ignore `buildFilesChild`. | 
| Lukacs T. Berki | 647e7ab | 2022-10-27 09:55:38 +0000 | [diff] [blame] | 423 | 			context.depCh <- srcChild | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 424 | 			if err := mergeBuildFiles(shared.JoinPath(context.topdir, forestChild), srcBuildFile, generatedBuildFile, context.verbose); err != nil { | 
| Cole Faust | 324a92e | 2022-08-23 15:29:05 -0700 | [diff] [blame] | 425 | 				fmt.Fprintf(os.Stderr, "Error merging %s and %s: %s", | 
 | 426 | 					srcBuildFile, generatedBuildFile, err) | 
| Usta Shrestha | 071f6c2 | 2022-10-25 12:34:06 -0400 | [diff] [blame] | 427 | 				os.Exit(1) | 
| Sasha Smundak | 0fd93e0 | 2022-05-19 19:34:31 -0700 | [diff] [blame] | 428 | 			} | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 429 | 		} else { | 
 | 430 | 			// Both exist and one is a file. This is an error. | 
 | 431 | 			fmt.Fprintf(os.Stderr, | 
| Lukacs T. Berki | b21166e | 2021-04-21 11:58:36 +0200 | [diff] [blame] | 432 | 				"Conflict in workspace symlink tree creation: both '%s' and '%s' exist and exactly one is a directory\n", | 
| Lukacs T. Berki | 3f9416e | 2021-04-20 13:08:11 +0200 | [diff] [blame] | 433 | 				srcChild, buildFilesChild) | 
| Usta Shrestha | 071f6c2 | 2022-10-25 12:34:06 -0400 | [diff] [blame] | 434 | 			os.Exit(1) | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 435 | 		} | 
 | 436 | 	} | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 437 |  | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 438 | 	// Remove all files in the forest that exist in neither the source | 
 | 439 | 	// tree nor the build files tree. (This handles files which were removed | 
 | 440 | 	// since the previous forest generation). | 
 | 441 | 	for f := range forestMapForDeletion { | 
 | 442 | 		var instructionsChild *instructionsNode | 
 | 443 | 		if instructions != nil { | 
 | 444 | 			instructionsChild = instructions.children[f] | 
| Lukacs T. Berki | bc5f731 | 2022-10-31 09:01:34 +0000 | [diff] [blame] | 445 | 		} | 
 | 446 |  | 
| Usta (Tsering) Shrestha | c4c07b1 | 2022-11-08 18:31:14 -0500 | [diff] [blame] | 447 | 		if instructionsChild != nil && instructionsChild.excluded { | 
 | 448 | 			// This directory may be excluded because bazel writes to it under the | 
 | 449 | 			// forest root. Thus this path is intentionally left alone. | 
 | 450 | 			continue | 
 | 451 | 		} | 
 | 452 | 		forestChild := shared.JoinPath(context.topdir, forestDir, f) | 
 | 453 | 		if err := os.RemoveAll(forestChild); err != nil { | 
 | 454 | 			fmt.Fprintf(os.Stderr, "Failed to remove '%s/%s': %s", forestDir, f, err) | 
| Lukacs T. Berki | bc5f731 | 2022-10-31 09:01:34 +0000 | [diff] [blame] | 455 | 			os.Exit(1) | 
 | 456 | 		} | 
| Lukacs T. Berki | bc5f731 | 2022-10-31 09:01:34 +0000 | [diff] [blame] | 457 | 	} | 
| Lukacs T. Berki | bc5f731 | 2022-10-31 09:01:34 +0000 | [diff] [blame] | 458 | } | 
 | 459 |  | 
| Usta Shrestha | da15c61 | 2022-11-08 14:12:36 -0500 | [diff] [blame] | 460 | // PlantSymlinkForest Creates a symlink forest by merging the directory tree at "buildFiles" and | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 461 | // "srcDir" while excluding paths listed in "exclude". Returns the set of paths | 
 | 462 | // under srcDir on which readdir() had to be called to produce the symlink | 
 | 463 | // forest. | 
| Usta Shrestha | da15c61 | 2022-11-08 14:12:36 -0500 | [diff] [blame] | 464 | func PlantSymlinkForest(verbose bool, topdir string, forest string, buildFiles string, exclude []string) (deps []string, mkdirCount, symlinkCount uint64) { | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 465 | 	context := &symlinkForestContext{ | 
| Usta Shrestha | da15c61 | 2022-11-08 14:12:36 -0500 | [diff] [blame] | 466 | 		verbose:      verbose, | 
 | 467 | 		topdir:       topdir, | 
 | 468 | 		depCh:        make(chan string), | 
 | 469 | 		mkdirCount:   atomic.Uint64{}, | 
 | 470 | 		symlinkCount: atomic.Uint64{}, | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 471 | 	} | 
 | 472 |  | 
| Mark Dacek | b2441c5 | 2023-10-04 16:12:00 +0000 | [diff] [blame] | 473 | 	// Check whether soong_build has been modified since the last run | 
 | 474 | 	soongBuildMTime, err := getSoongBuildMTime() | 
| Cole Faust | 5db9209 | 2023-04-05 11:36:23 -0700 | [diff] [blame] | 475 | 	if err != nil { | 
 | 476 | 		fmt.Fprintln(os.Stderr, err) | 
 | 477 | 		os.Exit(1) | 
 | 478 | 	} | 
 | 479 |  | 
| Mark Dacek | b2441c5 | 2023-10-04 16:12:00 +0000 | [diff] [blame] | 480 | 	shouldClean, err := shouldCleanSymlinkForest(topdir, forest, soongBuildMTime) | 
 | 481 |  | 
 | 482 | 	if err != nil { | 
 | 483 | 		fmt.Fprintln(os.Stderr, err) | 
 | 484 | 		os.Exit(1) | 
 | 485 | 	} else if shouldClean { | 
 | 486 | 		err = cleanSymlinkForest(topdir, forest) | 
 | 487 | 		if err != nil { | 
 | 488 | 			fmt.Fprintln(os.Stderr, err) | 
 | 489 | 			os.Exit(1) | 
 | 490 | 		} | 
 | 491 | 	} | 
 | 492 |  | 
| Lukacs T. Berki | c541cd2 | 2022-10-26 07:26:50 +0000 | [diff] [blame] | 493 | 	instructions := instructionsFromExcludePathList(exclude) | 
| Lukacs T. Berki | 647e7ab | 2022-10-27 09:55:38 +0000 | [diff] [blame] | 494 | 	go func() { | 
 | 495 | 		context.wg.Add(1) | 
 | 496 | 		plantSymlinkForestRecursive(context, instructions, forest, buildFiles, ".") | 
 | 497 | 		context.wg.Wait() | 
 | 498 | 		close(context.depCh) | 
 | 499 | 	}() | 
 | 500 |  | 
| Lukacs T. Berki | 647e7ab | 2022-10-27 09:55:38 +0000 | [diff] [blame] | 501 | 	for dep := range context.depCh { | 
 | 502 | 		deps = append(deps, dep) | 
 | 503 | 	} | 
 | 504 |  | 
| Mark Dacek | b2441c5 | 2023-10-04 16:12:00 +0000 | [diff] [blame] | 505 | 	err = writeSoongBuildMTimeFile(topdir, forest, soongBuildMTime) | 
| Cole Faust | 5db9209 | 2023-04-05 11:36:23 -0700 | [diff] [blame] | 506 | 	if err != nil { | 
 | 507 | 		fmt.Fprintln(os.Stderr, err) | 
 | 508 | 		os.Exit(1) | 
 | 509 | 	} | 
| Usta Shrestha | da15c61 | 2022-11-08 14:12:36 -0500 | [diff] [blame] | 510 | 	return deps, context.mkdirCount.Load(), context.symlinkCount.Load() | 
| Lukacs T. Berki | b353cca | 2021-04-16 13:47:36 +0200 | [diff] [blame] | 511 | } |