| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 1 | // Copyright 2017 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 |  | 
|  | 15 | package build | 
|  | 16 |  | 
|  | 17 | import ( | 
| Dan Willemsen | 1e775d7 | 2020-01-03 13:40:45 -0800 | [diff] [blame] | 18 | "bytes" | 
| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 19 | "fmt" | 
|  | 20 | "io/ioutil" | 
|  | 21 | "os" | 
|  | 22 | "path/filepath" | 
| Dan Willemsen | 1e775d7 | 2020-01-03 13:40:45 -0800 | [diff] [blame] | 23 | "sort" | 
| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 24 | "strings" | 
| Nan Zhang | 17f2767 | 2018-12-12 16:01:49 -0800 | [diff] [blame] | 25 |  | 
|  | 26 | "android/soong/ui/metrics" | 
| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 27 | ) | 
|  | 28 |  | 
|  | 29 | func removeGlobs(ctx Context, globs ...string) { | 
|  | 30 | for _, glob := range globs { | 
|  | 31 | files, err := filepath.Glob(glob) | 
|  | 32 | if err != nil { | 
|  | 33 | // Only possible error is ErrBadPattern | 
|  | 34 | panic(fmt.Errorf("%q: %s", glob, err)) | 
|  | 35 | } | 
|  | 36 |  | 
|  | 37 | for _, file := range files { | 
|  | 38 | err = os.RemoveAll(file) | 
|  | 39 | if err != nil { | 
|  | 40 | ctx.Fatalf("Failed to remove file %q: %v", file, err) | 
|  | 41 | } | 
|  | 42 | } | 
|  | 43 | } | 
|  | 44 | } | 
|  | 45 |  | 
|  | 46 | // Remove everything under the out directory. Don't remove the out directory | 
|  | 47 | // itself in case it's a symlink. | 
|  | 48 | func clean(ctx Context, config Config, what int) { | 
|  | 49 | removeGlobs(ctx, filepath.Join(config.OutDir(), "*")) | 
|  | 50 | ctx.Println("Entire build directory removed.") | 
|  | 51 | } | 
|  | 52 |  | 
|  | 53 | func dataClean(ctx Context, config Config, what int) { | 
|  | 54 | removeGlobs(ctx, filepath.Join(config.ProductOut(), "data", "*")) | 
|  | 55 | } | 
|  | 56 |  | 
|  | 57 | // installClean deletes all of the installed files -- the intent is to remove | 
|  | 58 | // files that may no longer be installed, either because the user previously | 
|  | 59 | // installed them, or they were previously installed by default but no longer | 
|  | 60 | // are. | 
|  | 61 | // | 
|  | 62 | // This is faster than a full clean, since we're not deleting the | 
|  | 63 | // intermediates.  Instead of recompiling, we can just copy the results. | 
|  | 64 | func installClean(ctx Context, config Config, what int) { | 
|  | 65 | dataClean(ctx, config, what) | 
|  | 66 |  | 
|  | 67 | if hostCrossOutPath := config.hostCrossOut(); hostCrossOutPath != "" { | 
|  | 68 | hostCrossOut := func(path string) string { | 
|  | 69 | return filepath.Join(hostCrossOutPath, path) | 
|  | 70 | } | 
|  | 71 | removeGlobs(ctx, | 
|  | 72 | hostCrossOut("bin"), | 
|  | 73 | hostCrossOut("coverage"), | 
|  | 74 | hostCrossOut("lib*"), | 
|  | 75 | hostCrossOut("nativetest*")) | 
|  | 76 | } | 
|  | 77 |  | 
|  | 78 | hostOutPath := config.HostOut() | 
|  | 79 | hostOut := func(path string) string { | 
|  | 80 | return filepath.Join(hostOutPath, path) | 
|  | 81 | } | 
|  | 82 |  | 
|  | 83 | productOutPath := config.ProductOut() | 
|  | 84 | productOut := func(path string) string { | 
|  | 85 | return filepath.Join(productOutPath, path) | 
|  | 86 | } | 
|  | 87 |  | 
|  | 88 | // Host bin, frameworks, and lib* are intentionally omitted, since | 
|  | 89 | // otherwise we'd have to rebuild any generated files created with | 
|  | 90 | // those tools. | 
|  | 91 | removeGlobs(ctx, | 
| Roland Levillain | e5f9ee5 | 2019-09-11 14:50:08 +0100 | [diff] [blame] | 92 | hostOut("apex"), | 
| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 93 | hostOut("obj/NOTICE_FILES"), | 
|  | 94 | hostOut("obj/PACKAGING"), | 
|  | 95 | hostOut("coverage"), | 
|  | 96 | hostOut("cts"), | 
|  | 97 | hostOut("nativetest*"), | 
|  | 98 | hostOut("sdk"), | 
|  | 99 | hostOut("sdk_addon"), | 
|  | 100 | hostOut("testcases"), | 
|  | 101 | hostOut("vts"), | 
| Dan Shi | 984c129 | 2020-03-18 22:42:00 -0700 | [diff] [blame] | 102 | hostOut("vts10"), | 
| Dan Shi | 53f1a19 | 2019-11-26 10:02:53 -0800 | [diff] [blame] | 103 | hostOut("vts-core"), | 
| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 104 | productOut("*.img"), | 
| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 105 | productOut("*.zip"), | 
| Dan Willemsen | a18660d | 2017-06-01 14:23:36 -0700 | [diff] [blame] | 106 | productOut("android-info.txt"), | 
| Steven Moreland | 0aabb11 | 2019-08-26 11:31:33 -0700 | [diff] [blame] | 107 | productOut("apex"), | 
| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 108 | productOut("kernel"), | 
|  | 109 | productOut("data"), | 
|  | 110 | productOut("skin"), | 
|  | 111 | productOut("obj/NOTICE_FILES"), | 
|  | 112 | productOut("obj/PACKAGING"), | 
| Tom Cherry | 7803a01 | 2018-08-08 13:24:32 -0700 | [diff] [blame] | 113 | productOut("ramdisk"), | 
| Bowgo Tsai | 5145c2c | 2019-10-08 18:12:37 +0800 | [diff] [blame] | 114 | productOut("debug_ramdisk"), | 
| Will McVicker | 4cee625 | 2020-03-19 11:57:11 -0700 | [diff] [blame] | 115 | productOut("vendor-ramdisk"), | 
|  | 116 | productOut("vendor-ramdisk-debug.cpio.gz"), | 
|  | 117 | productOut("vendor_debug_ramdisk"), | 
| Bowgo Tsai | 5145c2c | 2019-10-08 18:12:37 +0800 | [diff] [blame] | 118 | productOut("test_harness_ramdisk"), | 
| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 119 | productOut("recovery"), | 
|  | 120 | productOut("root"), | 
|  | 121 | productOut("system"), | 
|  | 122 | productOut("system_other"), | 
|  | 123 | productOut("vendor"), | 
| Jaekyun Seok | f6307cc | 2018-05-16 12:25:41 +0900 | [diff] [blame] | 124 | productOut("product"), | 
| Justin Yun | d5f6c82 | 2019-06-25 16:47:17 +0900 | [diff] [blame] | 125 | productOut("system_ext"), | 
| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 126 | productOut("oem"), | 
|  | 127 | productOut("obj/FAKE"), | 
|  | 128 | productOut("breakpad"), | 
|  | 129 | productOut("cache"), | 
|  | 130 | productOut("coverage"), | 
|  | 131 | productOut("installer"), | 
|  | 132 | productOut("odm"), | 
|  | 133 | productOut("sysloader"), | 
|  | 134 | productOut("testcases")) | 
|  | 135 | } | 
|  | 136 |  | 
|  | 137 | // Since products and build variants (unfortunately) shared the same | 
|  | 138 | // PRODUCT_OUT staging directory, things can get out of sync if different | 
|  | 139 | // build configurations are built in the same tree. This function will | 
|  | 140 | // notice when the configuration has changed and call installclean to | 
|  | 141 | // remove the files necessary to keep things consistent. | 
|  | 142 | func installCleanIfNecessary(ctx Context, config Config) { | 
|  | 143 | configFile := config.DevicePreviousProductConfig() | 
|  | 144 | prefix := "PREVIOUS_BUILD_CONFIG := " | 
|  | 145 | suffix := "\n" | 
|  | 146 | currentProduct := prefix + config.TargetProduct() + "-" + config.TargetBuildVariant() + suffix | 
|  | 147 |  | 
| Dan Willemsen | e0879fc | 2017-08-04 15:06:27 -0700 | [diff] [blame] | 148 | ensureDirectoriesExist(ctx, filepath.Dir(configFile)) | 
|  | 149 |  | 
| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 150 | writeConfig := func() { | 
|  | 151 | err := ioutil.WriteFile(configFile, []byte(currentProduct), 0666) | 
|  | 152 | if err != nil { | 
|  | 153 | ctx.Fatalln("Failed to write product config:", err) | 
|  | 154 | } | 
|  | 155 | } | 
|  | 156 |  | 
|  | 157 | prev, err := ioutil.ReadFile(configFile) | 
|  | 158 | if err != nil { | 
|  | 159 | if os.IsNotExist(err) { | 
|  | 160 | writeConfig() | 
|  | 161 | return | 
|  | 162 | } else { | 
|  | 163 | ctx.Fatalln("Failed to read previous product config:", err) | 
|  | 164 | } | 
|  | 165 | } else if string(prev) == currentProduct { | 
|  | 166 | return | 
|  | 167 | } | 
|  | 168 |  | 
|  | 169 | if disable, _ := config.Environment().Get("DISABLE_AUTO_INSTALLCLEAN"); disable == "true" { | 
|  | 170 | ctx.Println("DISABLE_AUTO_INSTALLCLEAN is set; skipping auto-clean. Your tree may be in an inconsistent state.") | 
|  | 171 | return | 
|  | 172 | } | 
|  | 173 |  | 
| Nan Zhang | 17f2767 | 2018-12-12 16:01:49 -0800 | [diff] [blame] | 174 | ctx.BeginTrace(metrics.PrimaryNinja, "installclean") | 
| Dan Willemsen | f052f78 | 2017-05-18 15:29:04 -0700 | [diff] [blame] | 175 | defer ctx.EndTrace() | 
|  | 176 |  | 
|  | 177 | prevConfig := strings.TrimPrefix(strings.TrimSuffix(string(prev), suffix), prefix) | 
|  | 178 | currentConfig := strings.TrimPrefix(strings.TrimSuffix(currentProduct, suffix), prefix) | 
|  | 179 |  | 
|  | 180 | ctx.Printf("Build configuration changed: %q -> %q, forcing installclean\n", prevConfig, currentConfig) | 
|  | 181 |  | 
|  | 182 | installClean(ctx, config, 0) | 
|  | 183 |  | 
|  | 184 | writeConfig() | 
|  | 185 | } | 
| Dan Willemsen | 1e775d7 | 2020-01-03 13:40:45 -0800 | [diff] [blame] | 186 |  | 
|  | 187 | // cleanOldFiles takes an input file (with all paths relative to basePath), and removes files from | 
|  | 188 | // the filesystem if they were removed from the input file since the last execution. | 
|  | 189 | func cleanOldFiles(ctx Context, basePath, file string) { | 
|  | 190 | file = filepath.Join(basePath, file) | 
|  | 191 | oldFile := file + ".previous" | 
|  | 192 |  | 
|  | 193 | if _, err := os.Stat(file); err != nil { | 
|  | 194 | ctx.Fatalf("Expected %q to be readable", file) | 
|  | 195 | } | 
|  | 196 |  | 
|  | 197 | if _, err := os.Stat(oldFile); os.IsNotExist(err) { | 
|  | 198 | if err := os.Rename(file, oldFile); err != nil { | 
|  | 199 | ctx.Fatalf("Failed to rename file list (%q->%q): %v", file, oldFile, err) | 
|  | 200 | } | 
|  | 201 | return | 
|  | 202 | } | 
|  | 203 |  | 
|  | 204 | var newPaths, oldPaths []string | 
|  | 205 | if newData, err := ioutil.ReadFile(file); err == nil { | 
|  | 206 | if oldData, err := ioutil.ReadFile(oldFile); err == nil { | 
|  | 207 | // Common case: nothing has changed | 
|  | 208 | if bytes.Equal(newData, oldData) { | 
|  | 209 | return | 
|  | 210 | } | 
|  | 211 | newPaths = strings.Fields(string(newData)) | 
|  | 212 | oldPaths = strings.Fields(string(oldData)) | 
|  | 213 | } else { | 
|  | 214 | ctx.Fatalf("Failed to read list of installable files (%q): %v", oldFile, err) | 
|  | 215 | } | 
|  | 216 | } else { | 
|  | 217 | ctx.Fatalf("Failed to read list of installable files (%q): %v", file, err) | 
|  | 218 | } | 
|  | 219 |  | 
|  | 220 | // These should be mostly sorted by make already, but better make sure Go concurs | 
|  | 221 | sort.Strings(newPaths) | 
|  | 222 | sort.Strings(oldPaths) | 
|  | 223 |  | 
|  | 224 | for len(oldPaths) > 0 { | 
|  | 225 | if len(newPaths) > 0 { | 
|  | 226 | if oldPaths[0] == newPaths[0] { | 
|  | 227 | // Same file; continue | 
|  | 228 | newPaths = newPaths[1:] | 
|  | 229 | oldPaths = oldPaths[1:] | 
|  | 230 | continue | 
|  | 231 | } else if oldPaths[0] > newPaths[0] { | 
|  | 232 | // New file; ignore | 
|  | 233 | newPaths = newPaths[1:] | 
|  | 234 | continue | 
|  | 235 | } | 
|  | 236 | } | 
|  | 237 | // File only exists in the old list; remove if it exists | 
|  | 238 | old := filepath.Join(basePath, oldPaths[0]) | 
|  | 239 | oldPaths = oldPaths[1:] | 
|  | 240 | if fi, err := os.Stat(old); err == nil { | 
|  | 241 | if fi.IsDir() { | 
|  | 242 | if err := os.Remove(old); err == nil { | 
|  | 243 | ctx.Println("Removed directory that is no longer installed: ", old) | 
| Dan Willemsen | 46459b0 | 2020-02-13 14:37:15 -0800 | [diff] [blame] | 244 | cleanEmptyDirs(ctx, filepath.Dir(old)) | 
| Dan Willemsen | 1e775d7 | 2020-01-03 13:40:45 -0800 | [diff] [blame] | 245 | } else { | 
|  | 246 | ctx.Println("Failed to remove directory that is no longer installed (%q): %v", old, err) | 
|  | 247 | ctx.Println("It's recommended to run `m installclean`") | 
|  | 248 | } | 
|  | 249 | } else { | 
|  | 250 | if err := os.Remove(old); err == nil { | 
|  | 251 | ctx.Println("Removed file that is no longer installed: ", old) | 
| Dan Willemsen | 46459b0 | 2020-02-13 14:37:15 -0800 | [diff] [blame] | 252 | cleanEmptyDirs(ctx, filepath.Dir(old)) | 
| Dan Willemsen | 1e775d7 | 2020-01-03 13:40:45 -0800 | [diff] [blame] | 253 | } else if !os.IsNotExist(err) { | 
|  | 254 | ctx.Fatalf("Failed to remove file that is no longer installed (%q): %v", old, err) | 
|  | 255 | } | 
|  | 256 | } | 
|  | 257 | } | 
|  | 258 | } | 
|  | 259 |  | 
|  | 260 | // Use the new list as the base for the next build | 
|  | 261 | os.Rename(file, oldFile) | 
|  | 262 | } | 
| Dan Willemsen | 46459b0 | 2020-02-13 14:37:15 -0800 | [diff] [blame] | 263 |  | 
|  | 264 | func cleanEmptyDirs(ctx Context, dir string) { | 
|  | 265 | files, err := ioutil.ReadDir(dir) | 
|  | 266 | if err != nil || len(files) > 0 { | 
|  | 267 | return | 
|  | 268 | } | 
|  | 269 | if err := os.Remove(dir); err == nil { | 
|  | 270 | ctx.Println("Removed directory that is no longer installed: ", dir) | 
|  | 271 | } else { | 
|  | 272 | ctx.Fatalf("Failed to remove directory that is no longer installed (%q): %v", dir, err) | 
|  | 273 | } | 
|  | 274 | cleanEmptyDirs(ctx, filepath.Dir(dir)) | 
|  | 275 | } |