github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/blueprint/bootstrap/cleanup.go (about)

     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  // 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.
    34  func 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  
    79  func 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  
   133  func 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  }