• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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// removeAbandonedFiles removes any files that appear in the Ninja log that are
32// not currently build targets.
33func 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
75func 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
124func 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