Home | History | Annotate | Download | only in bootstrap
      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