123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330 |
- // Copyright 2017 Google Inc. All rights reserved.
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package build
- import (
- "bytes"
- "fmt"
- "io/ioutil"
- "os"
- "path/filepath"
- "sort"
- "strings"
- "android/soong/ui/metrics"
- )
- // Given a series of glob patterns, remove matching files and directories from the filesystem.
- // For example, "malware*" would remove all files and directories in the current directory that begin with "malware".
- func removeGlobs(ctx Context, globs ...string) {
- for _, glob := range globs {
- // Find files and directories that match this glob pattern.
- files, err := filepath.Glob(glob)
- if err != nil {
- // Only possible error is ErrBadPattern
- panic(fmt.Errorf("%q: %s", glob, err))
- }
- for _, file := range files {
- err = os.RemoveAll(file)
- if err != nil {
- ctx.Fatalf("Failed to remove file %q: %v", file, err)
- }
- }
- }
- }
- // Based on https://stackoverflow.com/questions/28969455/how-to-properly-instantiate-os-filemode
- // Because Go doesn't provide a nice way to set bits on a filemode
- const (
- FILEMODE_READ = 04
- FILEMODE_WRITE = 02
- FILEMODE_EXECUTE = 01
- FILEMODE_USER_SHIFT = 6
- FILEMODE_USER_READ = FILEMODE_READ << FILEMODE_USER_SHIFT
- FILEMODE_USER_WRITE = FILEMODE_WRITE << FILEMODE_USER_SHIFT
- FILEMODE_USER_EXECUTE = FILEMODE_EXECUTE << FILEMODE_USER_SHIFT
- )
- // Remove everything under the out directory. Don't remove the out directory
- // itself in case it's a symlink.
- func clean(ctx Context, config Config) {
- removeGlobs(ctx, filepath.Join(config.OutDir(), "*"))
- ctx.Println("Entire build directory removed.")
- }
- // Remove everything in the data directory.
- func dataClean(ctx Context, config Config) {
- removeGlobs(ctx, filepath.Join(config.ProductOut(), "data", "*"))
- ctx.Println("Entire data directory removed.")
- }
- // installClean deletes all of the installed files -- the intent is to remove
- // files that may no longer be installed, either because the user previously
- // installed them, or they were previously installed by default but no longer
- // are.
- //
- // This is faster than a full clean, since we're not deleting the
- // intermediates. Instead of recompiling, we can just copy the results.
- func installClean(ctx Context, config Config) {
- dataClean(ctx, config)
- if hostCrossOutPath := config.hostCrossOut(); hostCrossOutPath != "" {
- hostCrossOut := func(path string) string {
- return filepath.Join(hostCrossOutPath, path)
- }
- removeGlobs(ctx,
- hostCrossOut("bin"),
- hostCrossOut("coverage"),
- hostCrossOut("lib*"),
- hostCrossOut("nativetest*"))
- }
- hostOutPath := config.HostOut()
- hostOut := func(path string) string {
- return filepath.Join(hostOutPath, path)
- }
- hostCommonOut := func(path string) string {
- return filepath.Join(config.hostOutRoot(), "common", path)
- }
- productOutPath := config.ProductOut()
- productOut := func(path string) string {
- return filepath.Join(productOutPath, path)
- }
- // Host bin, frameworks, and lib* are intentionally omitted, since
- // otherwise we'd have to rebuild any generated files created with
- // those tools.
- removeGlobs(ctx,
- hostOut("apex"),
- hostOut("obj/NOTICE_FILES"),
- hostOut("obj/PACKAGING"),
- hostOut("coverage"),
- hostOut("cts"),
- hostOut("nativetest*"),
- hostOut("sdk"),
- hostOut("sdk_addon"),
- hostOut("testcases"),
- hostOut("vts"),
- hostOut("vts10"),
- hostOut("vts-core"),
- hostCommonOut("obj/PACKAGING"),
- productOut("*.img"),
- productOut("*.zip"),
- productOut("android-info.txt"),
- productOut("misc_info.txt"),
- productOut("apex"),
- productOut("kernel"),
- productOut("kernel-*"),
- productOut("data"),
- productOut("skin"),
- productOut("obj/NOTICE_FILES"),
- productOut("obj/PACKAGING"),
- productOut("ramdisk"),
- productOut("ramdisk_16k"),
- productOut("debug_ramdisk"),
- productOut("vendor_ramdisk"),
- productOut("vendor_debug_ramdisk"),
- productOut("vendor_kernel_ramdisk"),
- productOut("test_harness_ramdisk"),
- productOut("recovery"),
- productOut("root"),
- productOut("system"),
- productOut("system_dlkm"),
- productOut("system_other"),
- productOut("vendor"),
- productOut("vendor_dlkm"),
- productOut("product"),
- productOut("system_ext"),
- productOut("oem"),
- productOut("obj/FAKE"),
- productOut("breakpad"),
- productOut("cache"),
- productOut("coverage"),
- productOut("installer"),
- productOut("odm"),
- productOut("odm_dlkm"),
- productOut("sysloader"),
- productOut("testcases"),
- productOut("symbols"))
- }
- // Since products and build variants (unfortunately) shared the same
- // PRODUCT_OUT staging directory, things can get out of sync if different
- // build configurations are built in the same tree. This function will
- // notice when the configuration has changed and call installClean to
- // remove the files necessary to keep things consistent.
- func installCleanIfNecessary(ctx Context, config Config) {
- configFile := config.DevicePreviousProductConfig()
- prefix := "PREVIOUS_BUILD_CONFIG := "
- suffix := "\n"
- currentConfig := prefix + config.TargetProduct() + "-" + config.TargetBuildVariant() + suffix
- ensureDirectoriesExist(ctx, filepath.Dir(configFile))
- writeConfig := func() {
- err := ioutil.WriteFile(configFile, []byte(currentConfig), 0666) // a+rw
- if err != nil {
- ctx.Fatalln("Failed to write product config:", err)
- }
- }
- previousConfigBytes, err := ioutil.ReadFile(configFile)
- if err != nil {
- if os.IsNotExist(err) {
- // Just write the new config file, no old config file to worry about.
- writeConfig()
- return
- } else {
- ctx.Fatalln("Failed to read previous product config:", err)
- }
- }
- previousConfig := string(previousConfigBytes)
- if previousConfig == currentConfig {
- // Same config as before - nothing to clean.
- return
- }
- if config.Environment().IsEnvTrue("DISABLE_AUTO_INSTALLCLEAN") {
- ctx.Println("DISABLE_AUTO_INSTALLCLEAN is set and true; skipping auto-clean. Your tree may be in an inconsistent state.")
- return
- }
- ctx.BeginTrace(metrics.PrimaryNinja, "installclean")
- defer ctx.EndTrace()
- previousProductAndVariant := strings.TrimPrefix(strings.TrimSuffix(previousConfig, suffix), prefix)
- currentProductAndVariant := strings.TrimPrefix(strings.TrimSuffix(currentConfig, suffix), prefix)
- ctx.Printf("Build configuration changed: %q -> %q, forcing installclean\n", previousProductAndVariant, currentProductAndVariant)
- installClean(ctx, config)
- writeConfig()
- }
- // cleanOldFiles takes an input file (with all paths relative to basePath), and removes files from
- // the filesystem if they were removed from the input file since the last execution.
- func cleanOldFiles(ctx Context, basePath, newFile string) {
- newFile = filepath.Join(basePath, newFile)
- oldFile := newFile + ".previous"
- if _, err := os.Stat(newFile); os.IsNotExist(err) {
- // If the file doesn't exist, assume no installed files exist either
- return
- } else if err != nil {
- ctx.Fatalf("Expected %q to be readable", newFile)
- }
- if _, err := os.Stat(oldFile); os.IsNotExist(err) {
- if err := os.Rename(newFile, oldFile); err != nil {
- ctx.Fatalf("Failed to rename file list (%q->%q): %v", newFile, oldFile, err)
- }
- return
- }
- var newData, oldData []byte
- if data, err := ioutil.ReadFile(newFile); err == nil {
- newData = data
- } else {
- ctx.Fatalf("Failed to read list of installable files (%q): %v", newFile, err)
- }
- if data, err := ioutil.ReadFile(oldFile); err == nil {
- oldData = data
- } else {
- ctx.Fatalf("Failed to read list of installable files (%q): %v", oldFile, err)
- }
- // Common case: nothing has changed
- if bytes.Equal(newData, oldData) {
- return
- }
- var newPaths, oldPaths []string
- newPaths = strings.Fields(string(newData))
- oldPaths = strings.Fields(string(oldData))
- // These should be mostly sorted by make already, but better make sure Go concurs
- sort.Strings(newPaths)
- sort.Strings(oldPaths)
- for len(oldPaths) > 0 {
- if len(newPaths) > 0 {
- if oldPaths[0] == newPaths[0] {
- // Same file; continue
- newPaths = newPaths[1:]
- oldPaths = oldPaths[1:]
- continue
- } else if oldPaths[0] > newPaths[0] {
- // New file; ignore
- newPaths = newPaths[1:]
- continue
- }
- }
- // File only exists in the old list; remove if it exists
- oldPath := filepath.Join(basePath, oldPaths[0])
- oldPaths = oldPaths[1:]
- if oldFile, err := os.Stat(oldPath); err == nil {
- if oldFile.IsDir() {
- if err := os.Remove(oldPath); err == nil {
- ctx.Println("Removed directory that is no longer installed: ", oldPath)
- cleanEmptyDirs(ctx, filepath.Dir(oldPath))
- } else {
- ctx.Println("Failed to remove directory that is no longer installed (%q): %v", oldPath, err)
- ctx.Println("It's recommended to run `m installclean`")
- }
- } else {
- // Removing a file, not a directory.
- if err := os.Remove(oldPath); err == nil {
- ctx.Println("Removed file that is no longer installed: ", oldPath)
- cleanEmptyDirs(ctx, filepath.Dir(oldPath))
- } else if !os.IsNotExist(err) {
- ctx.Fatalf("Failed to remove file that is no longer installed (%q): %v", oldPath, err)
- }
- }
- }
- }
- // Use the new list as the base for the next build
- os.Rename(newFile, oldFile)
- }
- // cleanEmptyDirs will delete a directory if it contains no files.
- // If a deletion occurs, then it also recurses upwards to try and delete empty parent directories.
- func cleanEmptyDirs(ctx Context, dir string) {
- files, err := ioutil.ReadDir(dir)
- if err != nil {
- ctx.Println("Could not read directory while trying to clean empty dirs: ", dir)
- return
- }
- if len(files) > 0 {
- // Directory is not empty.
- return
- }
- if err := os.Remove(dir); err == nil {
- ctx.Println("Removed empty directory (may no longer be installed?): ", dir)
- } else {
- ctx.Fatalf("Failed to remove empty directory (which may no longer be installed?) %q: (%v)", dir, err)
- }
- // Try and delete empty parent directories too.
- cleanEmptyDirs(ctx, filepath.Dir(dir))
- }
|