// Copyright 2014 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 bootstrap import ( "bufio" "errors" "fmt" "os" "path/filepath" "strings" "syscall" "github.com/google/blueprint" ) const logFileName = ".ninja_log" // removeAbandonedFilesUnder removes any files that appear in the Ninja log, and // are prefixed with one of the `under` entries, but that are not currently // build targets, or in `exempt` func removeAbandonedFilesUnder(ctx *blueprint.Context, config *Config, srcDir string, under, exempt []string) error { if len(under) == 0 { return nil } ninjaBuildDir, err := ctx.NinjaBuildDir() if err != nil { return err } targetRules, err := ctx.AllTargets() if err != nil { return fmt.Errorf("error determining target list: %s", err) } replacer := strings.NewReplacer( "@@SrcDir@@", srcDir, "@@BuildDir@@", BuildDir) ninjaBuildDir = replacer.Replace(ninjaBuildDir) targets := make(map[string]bool) for target := range targetRules { replacedTarget := replacer.Replace(target) targets[filepath.Clean(replacedTarget)] = true } for _, target := range exempt { replacedTarget := replacer.Replace(target) targets[filepath.Clean(replacedTarget)] = true } filePaths, err := parseNinjaLog(ninjaBuildDir, under) if err != nil { return err } for _, filePath := range filePaths { isTarget := targets[filePath] if !isTarget { err = removeFileAndEmptyDirs(filePath) if err != nil { return err } } } return nil } func parseNinjaLog(ninjaBuildDir string, under []string) ([]string, error) { logFilePath := filepath.Join(ninjaBuildDir, logFileName) logFile, err := os.Open(logFilePath) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, err } defer logFile.Close() scanner := bufio.NewScanner(logFile) // Check that the first line indicates that this is a Ninja log version 5 const expectedFirstLine = "# ninja log v5" if !scanner.Scan() || scanner.Text() != expectedFirstLine { return nil, errors.New("unrecognized ninja log format") } var filePaths []string for scanner.Scan() { line := scanner.Text() if strings.HasPrefix(line, "#") { continue } const fieldSeperator = "\t" fields := strings.Split(line, fieldSeperator) const precedingFields = 3 const followingFields = 1 if len(fields) < precedingFields+followingFields+1 { return nil, fmt.Errorf("log entry has too few fields: %q", line) } start := precedingFields end := len(fields) - followingFields filePath := strings.Join(fields[start:end], fieldSeperator) for _, dir := range under { if strings.HasPrefix(filePath, dir) { filePaths = append(filePaths, filePath) break } } } if err := scanner.Err(); err != nil { return nil, err } return filePaths, nil } func removeFileAndEmptyDirs(path string) error { err := os.Remove(path) if err != nil { if os.IsNotExist(err) { return nil } pathErr := err.(*os.PathError) switch pathErr.Err { case syscall.ENOTEMPTY, syscall.EEXIST, syscall.ENOTDIR: return nil } return err } fmt.Printf("removed old ninja-created file %s because it has no rule to generate it\n", path) path, err = filepath.Abs(path) if err != nil { return err } cwd, err := os.Getwd() if err != nil { return err } for dir := filepath.Dir(path); dir != cwd; dir = filepath.Dir(dir) { err = os.Remove(dir) if err != nil { pathErr := err.(*os.PathError) switch pathErr.Err { case syscall.ENOTEMPTY, syscall.EEXIST: // We've come to a nonempty directory, so we're done. return nil default: return err } } } return nil }