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