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