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 15package build 16 17import ( 18 "bytes" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "sort" 24 "strings" 25 26 "android/soong/ui/metrics" 27) 28 29func 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. 48func clean(ctx Context, config Config, what int) { 49 removeGlobs(ctx, filepath.Join(config.OutDir(), "*")) 50 ctx.Println("Entire build directory removed.") 51} 52 53func 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. 64func 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, 92 hostOut("apex"), 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"), 102 hostOut("vts10"), 103 hostOut("vts-core"), 104 productOut("*.img"), 105 productOut("*.zip"), 106 productOut("android-info.txt"), 107 productOut("misc_info.txt"), 108 productOut("apex"), 109 productOut("kernel"), 110 productOut("data"), 111 productOut("skin"), 112 productOut("obj/NOTICE_FILES"), 113 productOut("obj/PACKAGING"), 114 productOut("ramdisk"), 115 productOut("debug_ramdisk"), 116 productOut("vendor-ramdisk"), 117 productOut("vendor-ramdisk-debug.cpio.gz"), 118 productOut("vendor_debug_ramdisk"), 119 productOut("test_harness_ramdisk"), 120 productOut("recovery"), 121 productOut("root"), 122 productOut("system"), 123 productOut("system_other"), 124 productOut("vendor"), 125 productOut("product"), 126 productOut("system_ext"), 127 productOut("oem"), 128 productOut("obj/FAKE"), 129 productOut("breakpad"), 130 productOut("cache"), 131 productOut("coverage"), 132 productOut("installer"), 133 productOut("odm"), 134 productOut("sysloader"), 135 productOut("testcases")) 136} 137 138// Since products and build variants (unfortunately) shared the same 139// PRODUCT_OUT staging directory, things can get out of sync if different 140// build configurations are built in the same tree. This function will 141// notice when the configuration has changed and call installclean to 142// remove the files necessary to keep things consistent. 143func installCleanIfNecessary(ctx Context, config Config) { 144 configFile := config.DevicePreviousProductConfig() 145 prefix := "PREVIOUS_BUILD_CONFIG := " 146 suffix := "\n" 147 currentProduct := prefix + config.TargetProduct() + "-" + config.TargetBuildVariant() + suffix 148 149 ensureDirectoriesExist(ctx, filepath.Dir(configFile)) 150 151 writeConfig := func() { 152 err := ioutil.WriteFile(configFile, []byte(currentProduct), 0666) 153 if err != nil { 154 ctx.Fatalln("Failed to write product config:", err) 155 } 156 } 157 158 prev, err := ioutil.ReadFile(configFile) 159 if err != nil { 160 if os.IsNotExist(err) { 161 writeConfig() 162 return 163 } else { 164 ctx.Fatalln("Failed to read previous product config:", err) 165 } 166 } else if string(prev) == currentProduct { 167 return 168 } 169 170 if disable, _ := config.Environment().Get("DISABLE_AUTO_INSTALLCLEAN"); disable == "true" { 171 ctx.Println("DISABLE_AUTO_INSTALLCLEAN is set; skipping auto-clean. Your tree may be in an inconsistent state.") 172 return 173 } 174 175 ctx.BeginTrace(metrics.PrimaryNinja, "installclean") 176 defer ctx.EndTrace() 177 178 prevConfig := strings.TrimPrefix(strings.TrimSuffix(string(prev), suffix), prefix) 179 currentConfig := strings.TrimPrefix(strings.TrimSuffix(currentProduct, suffix), prefix) 180 181 ctx.Printf("Build configuration changed: %q -> %q, forcing installclean\n", prevConfig, currentConfig) 182 183 installClean(ctx, config, 0) 184 185 writeConfig() 186} 187 188// cleanOldFiles takes an input file (with all paths relative to basePath), and removes files from 189// the filesystem if they were removed from the input file since the last execution. 190func cleanOldFiles(ctx Context, basePath, file string) { 191 file = filepath.Join(basePath, file) 192 oldFile := file + ".previous" 193 194 if _, err := os.Stat(file); err != nil { 195 ctx.Fatalf("Expected %q to be readable", file) 196 } 197 198 if _, err := os.Stat(oldFile); os.IsNotExist(err) { 199 if err := os.Rename(file, oldFile); err != nil { 200 ctx.Fatalf("Failed to rename file list (%q->%q): %v", file, oldFile, err) 201 } 202 return 203 } 204 205 var newPaths, oldPaths []string 206 if newData, err := ioutil.ReadFile(file); err == nil { 207 if oldData, err := ioutil.ReadFile(oldFile); err == nil { 208 // Common case: nothing has changed 209 if bytes.Equal(newData, oldData) { 210 return 211 } 212 newPaths = strings.Fields(string(newData)) 213 oldPaths = strings.Fields(string(oldData)) 214 } else { 215 ctx.Fatalf("Failed to read list of installable files (%q): %v", oldFile, err) 216 } 217 } else { 218 ctx.Fatalf("Failed to read list of installable files (%q): %v", file, err) 219 } 220 221 // These should be mostly sorted by make already, but better make sure Go concurs 222 sort.Strings(newPaths) 223 sort.Strings(oldPaths) 224 225 for len(oldPaths) > 0 { 226 if len(newPaths) > 0 { 227 if oldPaths[0] == newPaths[0] { 228 // Same file; continue 229 newPaths = newPaths[1:] 230 oldPaths = oldPaths[1:] 231 continue 232 } else if oldPaths[0] > newPaths[0] { 233 // New file; ignore 234 newPaths = newPaths[1:] 235 continue 236 } 237 } 238 // File only exists in the old list; remove if it exists 239 old := filepath.Join(basePath, oldPaths[0]) 240 oldPaths = oldPaths[1:] 241 if fi, err := os.Stat(old); err == nil { 242 if fi.IsDir() { 243 if err := os.Remove(old); err == nil { 244 ctx.Println("Removed directory that is no longer installed: ", old) 245 cleanEmptyDirs(ctx, filepath.Dir(old)) 246 } else { 247 ctx.Println("Failed to remove directory that is no longer installed (%q): %v", old, err) 248 ctx.Println("It's recommended to run `m installclean`") 249 } 250 } else { 251 if err := os.Remove(old); err == nil { 252 ctx.Println("Removed file that is no longer installed: ", old) 253 cleanEmptyDirs(ctx, filepath.Dir(old)) 254 } else if !os.IsNotExist(err) { 255 ctx.Fatalf("Failed to remove file that is no longer installed (%q): %v", old, err) 256 } 257 } 258 } 259 } 260 261 // Use the new list as the base for the next build 262 os.Rename(file, oldFile) 263} 264 265func cleanEmptyDirs(ctx Context, dir string) { 266 files, err := ioutil.ReadDir(dir) 267 if err != nil || len(files) > 0 { 268 return 269 } 270 if err := os.Remove(dir); err == nil { 271 ctx.Println("Removed directory that is no longer installed: ", dir) 272 } else { 273 ctx.Fatalf("Failed to remove directory that is no longer installed (%q): %v", dir, err) 274 } 275 cleanEmptyDirs(ctx, filepath.Dir(dir)) 276} 277