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