blob: 9dbea2afc165efe7d24b830c613b74cc3b0c10bf [file] [log] [blame]
Colin Cross8e0c5112015-01-23 14:15:10 -08001// 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
Jamie Gennisaf435562014-10-27 22:34:56 -070015package bootstrap
16
17import (
Jamie Gennisaf435562014-10-27 22:34:56 -070018 "bufio"
19 "errors"
20 "fmt"
21 "os"
22 "path/filepath"
23 "strings"
24 "syscall"
Colin Crossa2599452015-11-18 16:01:01 -080025
26 "github.com/google/blueprint"
Jamie Gennisaf435562014-10-27 22:34:56 -070027)
28
29const logFileName = ".ninja_log"
30
Dan Willemsencd4e0ce2017-07-19 22:43:30 -070031// 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
Dan Willemsenab223a52018-07-05 21:56:59 -070033// build targets, or in `exempt`
Lukacs T. Berki53537442021-03-12 08:31:19 +010034func removeAbandonedFilesUnder(ctx *blueprint.Context,
35 srcDir, buildDir string, under, exempt []string) error {
Dan Willemsencd4e0ce2017-07-19 22:43:30 -070036
37 if len(under) == 0 {
38 return nil
39 }
Jamie Gennisc4ed7092014-11-09 11:58:40 -080040
Colin Crossa2599452015-11-18 16:01:01 -080041 ninjaBuildDir, err := ctx.NinjaBuildDir()
42 if err != nil {
43 return err
Jamie Gennisaf435562014-10-27 22:34:56 -070044 }
45
Jamie Gennisc4ed7092014-11-09 11:58:40 -080046 targetRules, err := ctx.AllTargets()
Jamie Gennisaf435562014-10-27 22:34:56 -070047 if err != nil {
Jamie Gennisc4ed7092014-11-09 11:58:40 -080048 return fmt.Errorf("error determining target list: %s", err)
49 }
50
51 replacer := strings.NewReplacer(
52 "@@SrcDir@@", srcDir,
Lukacs T. Berki53537442021-03-12 08:31:19 +010053 "@@BuildDir@@", buildDir)
Colin Crossa2599452015-11-18 16:01:01 -080054 ninjaBuildDir = replacer.Replace(ninjaBuildDir)
Jamie Gennisc4ed7092014-11-09 11:58:40 -080055 targets := make(map[string]bool)
56 for target := range targetRules {
57 replacedTarget := replacer.Replace(target)
Dan Willemsenf0ca9012015-07-13 18:11:49 -070058 targets[filepath.Clean(replacedTarget)] = true
Jamie Gennisaf435562014-10-27 22:34:56 -070059 }
Dan Willemsenab223a52018-07-05 21:56:59 -070060 for _, target := range exempt {
61 replacedTarget := replacer.Replace(target)
62 targets[filepath.Clean(replacedTarget)] = true
63 }
Jamie Gennisaf435562014-10-27 22:34:56 -070064
Dan Willemsencd4e0ce2017-07-19 22:43:30 -070065 filePaths, err := parseNinjaLog(ninjaBuildDir, under)
Jamie Gennisaf435562014-10-27 22:34:56 -070066 if err != nil {
67 return err
68 }
69
70 for _, filePath := range filePaths {
Jamie Gennisc4ed7092014-11-09 11:58:40 -080071 isTarget := targets[filePath]
Jamie Gennisaf435562014-10-27 22:34:56 -070072 if !isTarget {
Colin Crossc5fa50e2019-12-17 13:12:35 -080073 err = removeFileAndEmptyDirs(absolutePath(filePath))
Jamie Gennisaf435562014-10-27 22:34:56 -070074 if err != nil {
75 return err
76 }
77 }
78 }
79
80 return nil
81}
82
Dan Willemsencd4e0ce2017-07-19 22:43:30 -070083func parseNinjaLog(ninjaBuildDir string, under []string) ([]string, error) {
Colin Crossa2599452015-11-18 16:01:01 -080084 logFilePath := filepath.Join(ninjaBuildDir, logFileName)
Jamie Gennisaf435562014-10-27 22:34:56 -070085 logFile, err := os.Open(logFilePath)
86 if err != nil {
87 if os.IsNotExist(err) {
88 return nil, nil
89 }
90 return nil, err
91 }
92 defer logFile.Close()
93
94 scanner := bufio.NewScanner(logFile)
95
96 // Check that the first line indicates that this is a Ninja log version 5
97 const expectedFirstLine = "# ninja log v5"
98 if !scanner.Scan() || scanner.Text() != expectedFirstLine {
99 return nil, errors.New("unrecognized ninja log format")
100 }
101
102 var filePaths []string
103 for scanner.Scan() {
104 line := scanner.Text()
105 if strings.HasPrefix(line, "#") {
106 continue
107 }
108
109 const fieldSeperator = "\t"
110 fields := strings.Split(line, fieldSeperator)
111
112 const precedingFields = 3
113 const followingFields = 1
114
115 if len(fields) < precedingFields+followingFields+1 {
116 return nil, fmt.Errorf("log entry has too few fields: %q", line)
117 }
118
119 start := precedingFields
120 end := len(fields) - followingFields
121 filePath := strings.Join(fields[start:end], fieldSeperator)
122
Dan Willemsencd4e0ce2017-07-19 22:43:30 -0700123 for _, dir := range under {
124 if strings.HasPrefix(filePath, dir) {
125 filePaths = append(filePaths, filePath)
126 break
127 }
128 }
Jamie Gennisaf435562014-10-27 22:34:56 -0700129 }
130 if err := scanner.Err(); err != nil {
131 return nil, err
132 }
133
134 return filePaths, nil
135}
136
137func removeFileAndEmptyDirs(path string) error {
138 err := os.Remove(path)
139 if err != nil {
140 if os.IsNotExist(err) {
141 return nil
142 }
Colin Cross93ef72d2015-02-24 17:13:12 -0800143 pathErr := err.(*os.PathError)
144 switch pathErr.Err {
Colin Crossca381712015-11-17 16:18:13 -0800145 case syscall.ENOTEMPTY, syscall.EEXIST, syscall.ENOTDIR:
Colin Cross93ef72d2015-02-24 17:13:12 -0800146 return nil
147 }
Jamie Gennisaf435562014-10-27 22:34:56 -0700148 return err
149 }
Colin Crossd9d92cb2015-02-12 10:24:29 -0800150 fmt.Printf("removed old ninja-created file %s because it has no rule to generate it\n", path)
Jamie Gennisaf435562014-10-27 22:34:56 -0700151
152 path, err = filepath.Abs(path)
153 if err != nil {
154 return err
155 }
156
157 cwd, err := os.Getwd()
158 if err != nil {
159 return err
160 }
161
162 for dir := filepath.Dir(path); dir != cwd; dir = filepath.Dir(dir) {
163 err = os.Remove(dir)
164 if err != nil {
165 pathErr := err.(*os.PathError)
166 switch pathErr.Err {
167 case syscall.ENOTEMPTY, syscall.EEXIST:
168 // We've come to a nonempty directory, so we're done.
169 return nil
170 default:
171 return err
172 }
173 }
174 }
175
176 return nil
177}