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