• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 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 build
16
17import (
18	"bytes"
19	"fmt"
20	"io/fs"
21	"io/ioutil"
22	"os"
23	"path/filepath"
24	"sort"
25	"strings"
26
27	"android/soong/ui/metrics"
28)
29
30// Given a series of glob patterns, remove matching files and directories from the filesystem.
31// For example, "malware*" would remove all files and directories in the current directory that begin with "malware".
32func removeGlobs(ctx Context, globs ...string) {
33	for _, glob := range globs {
34		// Find files and directories that match this glob pattern.
35		files, err := filepath.Glob(glob)
36		if err != nil {
37			// Only possible error is ErrBadPattern
38			panic(fmt.Errorf("%q: %s", glob, err))
39		}
40
41		for _, file := range files {
42			err = os.RemoveAll(file)
43			if err != nil {
44				ctx.Fatalf("Failed to remove file %q: %v", file, err)
45			}
46		}
47	}
48}
49
50// Based on https://stackoverflow.com/questions/28969455/how-to-properly-instantiate-os-filemode
51// Because Go doesn't provide a nice way to set bits on a filemode
52const (
53	FILEMODE_READ         = 04
54	FILEMODE_WRITE        = 02
55	FILEMODE_EXECUTE      = 01
56	FILEMODE_USER_SHIFT   = 6
57	FILEMODE_USER_READ    = FILEMODE_READ << FILEMODE_USER_SHIFT
58	FILEMODE_USER_WRITE   = FILEMODE_WRITE << FILEMODE_USER_SHIFT
59	FILEMODE_USER_EXECUTE = FILEMODE_EXECUTE << FILEMODE_USER_SHIFT
60)
61
62// Ensures that files and directories in the out dir can be deleted.
63// For example, Bazen can generate output directories where the write bit isn't set, causing 'm' clean' to fail.
64func ensureOutDirRemovable(ctx Context, config Config) {
65	err := filepath.WalkDir(config.OutDir(), func(path string, d fs.DirEntry, err error) error {
66		if err != nil {
67			return err
68		}
69		if d.IsDir() {
70			info, err := d.Info()
71			if err != nil {
72				return err
73			}
74			// Equivalent to running chmod u+rwx on each directory
75			newMode := info.Mode() | FILEMODE_USER_READ | FILEMODE_USER_WRITE | FILEMODE_USER_EXECUTE
76			if err := os.Chmod(path, newMode); err != nil {
77				return err
78			}
79		}
80		// Continue walking the out dir...
81		return nil
82	})
83	if err != nil && !os.IsNotExist(err) {
84		// Display the error, but don't crash.
85		ctx.Println(err.Error())
86	}
87}
88
89// Remove everything under the out directory. Don't remove the out directory
90// itself in case it's a symlink.
91func clean(ctx Context, config Config) {
92	ensureOutDirRemovable(ctx, config)
93	removeGlobs(ctx, filepath.Join(config.OutDir(), "*"))
94	ctx.Println("Entire build directory removed.")
95}
96
97// Remove everything in the data directory.
98func dataClean(ctx Context, config Config) {
99	removeGlobs(ctx, filepath.Join(config.ProductOut(), "data", "*"))
100	ctx.Println("Entire data directory removed.")
101}
102
103// installClean deletes all of the installed files -- the intent is to remove
104// files that may no longer be installed, either because the user previously
105// installed them, or they were previously installed by default but no longer
106// are.
107//
108// This is faster than a full clean, since we're not deleting the
109// intermediates.  Instead of recompiling, we can just copy the results.
110func installClean(ctx Context, config Config) {
111	dataClean(ctx, config)
112
113	if hostCrossOutPath := config.hostCrossOut(); hostCrossOutPath != "" {
114		hostCrossOut := func(path string) string {
115			return filepath.Join(hostCrossOutPath, path)
116		}
117		removeGlobs(ctx,
118			hostCrossOut("bin"),
119			hostCrossOut("coverage"),
120			hostCrossOut("lib*"),
121			hostCrossOut("nativetest*"))
122	}
123
124	hostOutPath := config.HostOut()
125	hostOut := func(path string) string {
126		return filepath.Join(hostOutPath, path)
127	}
128
129	hostCommonOut := func(path string) string {
130		return filepath.Join(config.hostOutRoot(), "common", path)
131	}
132
133	productOutPath := config.ProductOut()
134	productOut := func(path string) string {
135		return filepath.Join(productOutPath, path)
136	}
137
138	// Host bin, frameworks, and lib* are intentionally omitted, since
139	// otherwise we'd have to rebuild any generated files created with
140	// those tools.
141	removeGlobs(ctx,
142		hostOut("apex"),
143		hostOut("obj/NOTICE_FILES"),
144		hostOut("obj/PACKAGING"),
145		hostOut("coverage"),
146		hostOut("cts"),
147		hostOut("nativetest*"),
148		hostOut("sdk"),
149		hostOut("sdk_addon"),
150		hostOut("testcases"),
151		hostOut("vts"),
152		hostOut("vts10"),
153		hostOut("vts-core"),
154		hostCommonOut("obj/PACKAGING"),
155		productOut("*.img"),
156		productOut("*.zip"),
157		productOut("android-info.txt"),
158		productOut("misc_info.txt"),
159		productOut("apex"),
160		productOut("kernel"),
161		productOut("kernel-*"),
162		productOut("data"),
163		productOut("skin"),
164		productOut("obj/NOTICE_FILES"),
165		productOut("obj/PACKAGING"),
166		productOut("ramdisk"),
167		productOut("debug_ramdisk"),
168		productOut("vendor_ramdisk"),
169		productOut("vendor_debug_ramdisk"),
170		productOut("vendor_kernel_ramdisk"),
171		productOut("test_harness_ramdisk"),
172		productOut("recovery"),
173		productOut("root"),
174		productOut("system"),
175		productOut("system_dlkm"),
176		productOut("system_other"),
177		productOut("vendor"),
178		productOut("vendor_dlkm"),
179		productOut("product"),
180		productOut("system_ext"),
181		productOut("oem"),
182		productOut("obj/FAKE"),
183		productOut("breakpad"),
184		productOut("cache"),
185		productOut("coverage"),
186		productOut("installer"),
187		productOut("odm"),
188		productOut("odm_dlkm"),
189		productOut("sysloader"),
190		productOut("testcases"),
191		productOut("symbols"))
192}
193
194// Since products and build variants (unfortunately) shared the same
195// PRODUCT_OUT staging directory, things can get out of sync if different
196// build configurations are built in the same tree. This function will
197// notice when the configuration has changed and call installClean to
198// remove the files necessary to keep things consistent.
199func installCleanIfNecessary(ctx Context, config Config) {
200	configFile := config.DevicePreviousProductConfig()
201	prefix := "PREVIOUS_BUILD_CONFIG := "
202	suffix := "\n"
203	currentConfig := prefix + config.TargetProduct() + "-" + config.TargetBuildVariant() + suffix
204
205	ensureDirectoriesExist(ctx, filepath.Dir(configFile))
206
207	writeConfig := func() {
208		err := ioutil.WriteFile(configFile, []byte(currentConfig), 0666) // a+rw
209		if err != nil {
210			ctx.Fatalln("Failed to write product config:", err)
211		}
212	}
213
214	previousConfigBytes, err := ioutil.ReadFile(configFile)
215	if err != nil {
216		if os.IsNotExist(err) {
217			// Just write the new config file, no old config file to worry about.
218			writeConfig()
219			return
220		} else {
221			ctx.Fatalln("Failed to read previous product config:", err)
222		}
223	}
224
225	previousConfig := string(previousConfigBytes)
226	if previousConfig == currentConfig {
227		// Same config as before - nothing to clean.
228		return
229	}
230
231	if config.Environment().IsEnvTrue("DISABLE_AUTO_INSTALLCLEAN") {
232		ctx.Println("DISABLE_AUTO_INSTALLCLEAN is set and true; skipping auto-clean. Your tree may be in an inconsistent state.")
233		return
234	}
235
236	ctx.BeginTrace(metrics.PrimaryNinja, "installclean")
237	defer ctx.EndTrace()
238
239	previousProductAndVariant := strings.TrimPrefix(strings.TrimSuffix(previousConfig, suffix), prefix)
240	currentProductAndVariant := strings.TrimPrefix(strings.TrimSuffix(currentConfig, suffix), prefix)
241
242	ctx.Printf("Build configuration changed: %q -> %q, forcing installclean\n", previousProductAndVariant, currentProductAndVariant)
243
244	installClean(ctx, config)
245
246	writeConfig()
247}
248
249// cleanOldFiles takes an input file (with all paths relative to basePath), and removes files from
250// the filesystem if they were removed from the input file since the last execution.
251func cleanOldFiles(ctx Context, basePath, newFile string) {
252	newFile = filepath.Join(basePath, newFile)
253	oldFile := newFile + ".previous"
254
255	if _, err := os.Stat(newFile); os.IsNotExist(err) {
256		// If the file doesn't exist, assume no installed files exist either
257		return
258	} else if err != nil {
259		ctx.Fatalf("Expected %q to be readable", newFile)
260	}
261
262	if _, err := os.Stat(oldFile); os.IsNotExist(err) {
263		if err := os.Rename(newFile, oldFile); err != nil {
264			ctx.Fatalf("Failed to rename file list (%q->%q): %v", newFile, oldFile, err)
265		}
266		return
267	}
268
269	var newData, oldData []byte
270	if data, err := ioutil.ReadFile(newFile); err == nil {
271		newData = data
272	} else {
273		ctx.Fatalf("Failed to read list of installable files (%q): %v", newFile, err)
274	}
275	if data, err := ioutil.ReadFile(oldFile); err == nil {
276		oldData = data
277	} else {
278		ctx.Fatalf("Failed to read list of installable files (%q): %v", oldFile, err)
279	}
280
281	// Common case: nothing has changed
282	if bytes.Equal(newData, oldData) {
283		return
284	}
285
286	var newPaths, oldPaths []string
287	newPaths = strings.Fields(string(newData))
288	oldPaths = strings.Fields(string(oldData))
289
290	// These should be mostly sorted by make already, but better make sure Go concurs
291	sort.Strings(newPaths)
292	sort.Strings(oldPaths)
293
294	for len(oldPaths) > 0 {
295		if len(newPaths) > 0 {
296			if oldPaths[0] == newPaths[0] {
297				// Same file; continue
298				newPaths = newPaths[1:]
299				oldPaths = oldPaths[1:]
300				continue
301			} else if oldPaths[0] > newPaths[0] {
302				// New file; ignore
303				newPaths = newPaths[1:]
304				continue
305			}
306		}
307
308		// File only exists in the old list; remove if it exists
309		oldPath := filepath.Join(basePath, oldPaths[0])
310		oldPaths = oldPaths[1:]
311
312		if oldFile, err := os.Stat(oldPath); err == nil {
313			if oldFile.IsDir() {
314				if err := os.Remove(oldPath); err == nil {
315					ctx.Println("Removed directory that is no longer installed: ", oldPath)
316					cleanEmptyDirs(ctx, filepath.Dir(oldPath))
317				} else {
318					ctx.Println("Failed to remove directory that is no longer installed (%q): %v", oldPath, err)
319					ctx.Println("It's recommended to run `m installclean`")
320				}
321			} else {
322				// Removing a file, not a directory.
323				if err := os.Remove(oldPath); err == nil {
324					ctx.Println("Removed file that is no longer installed: ", oldPath)
325					cleanEmptyDirs(ctx, filepath.Dir(oldPath))
326				} else if !os.IsNotExist(err) {
327					ctx.Fatalf("Failed to remove file that is no longer installed (%q): %v", oldPath, err)
328				}
329			}
330		}
331	}
332
333	// Use the new list as the base for the next build
334	os.Rename(newFile, oldFile)
335}
336
337// cleanEmptyDirs will delete a directory if it contains no files.
338// If a deletion occurs, then it also recurses upwards to try and delete empty parent directories.
339func cleanEmptyDirs(ctx Context, dir string) {
340	files, err := ioutil.ReadDir(dir)
341	if err != nil {
342		ctx.Println("Could not read directory while trying to clean empty dirs: ", dir)
343		return
344	}
345	if len(files) > 0 {
346		// Directory is not empty.
347		return
348	}
349
350	if err := os.Remove(dir); err == nil {
351		ctx.Println("Removed empty directory (may no longer be installed?): ", dir)
352	} else {
353		ctx.Fatalf("Failed to remove empty directory (which may no longer be installed?) %q: (%v)", dir, err)
354	}
355
356	// Try and delete empty parent directories too.
357	cleanEmptyDirs(ctx, filepath.Dir(dir))
358}
359