1// Copyright 2014 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 bootstrap 16 17import ( 18 "bufio" 19 "errors" 20 "fmt" 21 "os" 22 "path/filepath" 23 "strings" 24 "syscall" 25 26 "github.com/google/blueprint" 27) 28 29const logFileName = ".ninja_log" 30 31// removeAbandonedFiles removes any files that appear in the Ninja log that are 32// not currently build targets. 33func removeAbandonedFiles(ctx *blueprint.Context, config *Config, 34 srcDir, manifestFile string) error { 35 36 ninjaBuildDir, err := ctx.NinjaBuildDir() 37 if err != nil { 38 return err 39 } 40 41 targetRules, err := ctx.AllTargets() 42 if err != nil { 43 return fmt.Errorf("error determining target list: %s", err) 44 } 45 46 replacer := strings.NewReplacer( 47 "@@SrcDir@@", srcDir, 48 "@@BuildDir@@", BuildDir, 49 "@@BootstrapManifest@@", manifestFile) 50 ninjaBuildDir = replacer.Replace(ninjaBuildDir) 51 targets := make(map[string]bool) 52 for target := range targetRules { 53 replacedTarget := replacer.Replace(target) 54 targets[filepath.Clean(replacedTarget)] = true 55 } 56 57 filePaths, err := parseNinjaLog(ninjaBuildDir) 58 if err != nil { 59 return err 60 } 61 62 for _, filePath := range filePaths { 63 isTarget := targets[filePath] 64 if !isTarget { 65 err = removeFileAndEmptyDirs(filePath) 66 if err != nil { 67 return err 68 } 69 } 70 } 71 72 return nil 73} 74 75func parseNinjaLog(ninjaBuildDir string) ([]string, error) { 76 logFilePath := filepath.Join(ninjaBuildDir, logFileName) 77 logFile, err := os.Open(logFilePath) 78 if err != nil { 79 if os.IsNotExist(err) { 80 return nil, nil 81 } 82 return nil, err 83 } 84 defer logFile.Close() 85 86 scanner := bufio.NewScanner(logFile) 87 88 // Check that the first line indicates that this is a Ninja log version 5 89 const expectedFirstLine = "# ninja log v5" 90 if !scanner.Scan() || scanner.Text() != expectedFirstLine { 91 return nil, errors.New("unrecognized ninja log format") 92 } 93 94 var filePaths []string 95 for scanner.Scan() { 96 line := scanner.Text() 97 if strings.HasPrefix(line, "#") { 98 continue 99 } 100 101 const fieldSeperator = "\t" 102 fields := strings.Split(line, fieldSeperator) 103 104 const precedingFields = 3 105 const followingFields = 1 106 107 if len(fields) < precedingFields+followingFields+1 { 108 return nil, fmt.Errorf("log entry has too few fields: %q", line) 109 } 110 111 start := precedingFields 112 end := len(fields) - followingFields 113 filePath := strings.Join(fields[start:end], fieldSeperator) 114 115 filePaths = append(filePaths, filePath) 116 } 117 if err := scanner.Err(); err != nil { 118 return nil, err 119 } 120 121 return filePaths, nil 122} 123 124func removeFileAndEmptyDirs(path string) error { 125 err := os.Remove(path) 126 if err != nil { 127 if os.IsNotExist(err) { 128 return nil 129 } 130 pathErr := err.(*os.PathError) 131 switch pathErr.Err { 132 case syscall.ENOTEMPTY, syscall.EEXIST, syscall.ENOTDIR: 133 return nil 134 } 135 return err 136 } 137 fmt.Printf("removed old ninja-created file %s because it has no rule to generate it\n", path) 138 139 path, err = filepath.Abs(path) 140 if err != nil { 141 return err 142 } 143 144 cwd, err := os.Getwd() 145 if err != nil { 146 return err 147 } 148 149 for dir := filepath.Dir(path); dir != cwd; dir = filepath.Dir(dir) { 150 err = os.Remove(dir) 151 if err != nil { 152 pathErr := err.(*os.PathError) 153 switch pathErr.Err { 154 case syscall.ENOTEMPTY, syscall.EEXIST: 155 // We've come to a nonempty directory, so we're done. 156 return nil 157 default: 158 return err 159 } 160 } 161 } 162 163 return nil 164} 165