aboutsummaryrefslogtreecommitdiff
path: root/ui/build/finder.go
blob: 8407f63971f675d9281ac61ec5b5bae8ddc004f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package build

import (
	"bytes"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	"android/soong/finder"
	"android/soong/finder/fs"
	"android/soong/ui/logger"

	"android/soong/ui/metrics"
)

// This file provides an interface to the Finder type for soong_ui. Finder is
// used to recursively traverse the source tree to gather paths of files, such
// as Android.bp or Android.mk, and store the lists/database of paths in files
// under `$OUT_DIR/.module_paths`. This directory can also be dist'd.

// NewSourceFinder returns a new Finder configured to search for source files.
// Callers of NewSourceFinder should call <f.Shutdown()> when done
func NewSourceFinder(ctx Context, config Config) (f *finder.Finder) {
	ctx.BeginTrace(metrics.RunSetupTool, "find modules")
	defer ctx.EndTrace()

	// Set up the working directory for the Finder.
	dir, err := os.Getwd()
	if err != nil {
		ctx.Fatalf("No working directory for module-finder: %v", err.Error())
	}
	filesystem := fs.OsFs

	// .out-dir and .find-ignore are markers for Finder to ignore siblings and
	// subdirectories of the directory Finder finds them in, hence stopping the
	// search recursively down those branches. It's possible that these files
	// are in the root directory, and if they are, then the subsequent error
	// messages are very confusing, so check for that here.
	pruneFiles := []string{".out-dir", ".find-ignore"}
	for _, name := range pruneFiles {
		prunePath := filepath.Join(dir, name)
		_, statErr := filesystem.Lstat(prunePath)
		if statErr == nil {
			ctx.Fatalf("%v must not exist", prunePath)
		}
	}

	// Set up configuration parameters for the Finder cache.
	cacheParams := finder.CacheParams{
		WorkingDirectory: dir,
		RootDirs:         []string{"."},
		FollowSymlinks:   config.environ.IsEnvTrue("ALLOW_BP_UNDER_SYMLINKS"),
		ExcludeDirs:      []string{".git", ".repo"},
		PruneFiles:       pruneFiles,
		IncludeFiles: []string{
			// Kati build definitions.
			"Android.mk",
			// Product configuration files.
			"AndroidProducts.mk",
			// General Soong build definitions, using the Blueprint syntax.
			"Android.bp",
			// Kati clean definitions.
			"CleanSpec.mk",
			// Ownership definition.
			"OWNERS",
			// Test configuration for modules in directories that contain this
			// file.
			"TEST_MAPPING",
			// METADATA file of packages
			"METADATA",
			// Release config contributions.
			"release_config_map.textproto",
			// Release config allowed duplicate flag declarations.
			"duplicate_allowlist.txt",
		},
		IncludeSuffixes: []string{
			// .mk files for product/board configuration.
			".mk",
			// otatools cert files
			".pk8",
			".pem",
			".avbpubkey",
		},
	}
	dumpDir := config.FileListDir()
	f, err = finder.New(cacheParams, filesystem, logger.New(ioutil.Discard),
		filepath.Join(dumpDir, "files.db"))
	if err != nil {
		ctx.Fatalf("Could not create module-finder: %v", err)
	}
	return f
}

func findProductAndBoardConfigFiles(entries finder.DirEntries) (dirNames []string, fileNames []string) {
	matches := []string{}
	for _, foundName := range entries.FileNames {
		if foundName != "Android.mk" &&
			foundName != "AndroidProducts.mk" &&
			foundName != "CleanSpec.mk" &&
			strings.HasSuffix(foundName, ".mk") {
			matches = append(matches, foundName)
		}
	}
	return entries.DirNames, matches
}

func findOtaToolsCertFiles(entries finder.DirEntries) (dirNames []string, fileNames []string) {
	matches := []string{}
	for _, foundName := range entries.FileNames {
		if strings.HasSuffix(foundName, ".pk8") ||
			strings.HasSuffix(foundName, ".pem") ||
			strings.HasSuffix(foundName, ".avbpubkey") {
			matches = append(matches, foundName)
		}
	}
	return entries.DirNames, matches
}

// FindSources searches for source files known to <f> and writes them to the filesystem for
// use later.
func FindSources(ctx Context, config Config, f *finder.Finder) {
	// note that dumpDir in FindSources may be different than dumpDir in NewSourceFinder
	// if a caller such as multiproduct_kati wants to share one Finder among several builds
	dumpDir := config.FileListDir()
	os.MkdirAll(dumpDir, 0777)

	// Stop searching a subdirectory recursively after finding an Android.mk.
	androidMks := f.FindFirstNamedAt(".", "Android.mk")
	androidMks = ignoreSomeAndroidMks(androidMks)
	blockAndroidMks(ctx, androidMks)
	err := dumpListToFile(ctx, config, androidMks, filepath.Join(dumpDir, "Android.mk.list"))
	if err != nil {
		ctx.Fatalf("Could not export module list: %v", err)
	}

	// Gate collecting/reporting mk metrics on builds that specifically request
	// it, as identifying the total number of mk files adds 4-5ms onto null
	// builds.
	if config.reportMkMetrics {
		androidMksTotal := f.FindNamedAt(".", "Android.mk")

		ctx.Metrics.SetToplevelMakefiles(len(androidMks))
		ctx.Metrics.SetTotalMakefiles(len(androidMksTotal))
		ctx.Metrics.DumpMkMetrics(config.MkMetrics())
	}

	// Stop searching a subdirectory recursively after finding a CleanSpec.mk.
	cleanSpecs := f.FindFirstNamedAt(".", "CleanSpec.mk")
	err = dumpListToFile(ctx, config, cleanSpecs, filepath.Join(dumpDir, "CleanSpec.mk.list"))
	if err != nil {
		ctx.Fatalf("Could not export module list: %v", err)
	}

	// Only consider AndroidProducts.mk in device/, vendor/ and product/, recursively in these directories.
	androidProductsMks := f.FindNamedAt("device", "AndroidProducts.mk")
	androidProductsMks = append(androidProductsMks, f.FindNamedAt("vendor", "AndroidProducts.mk")...)
	androidProductsMks = append(androidProductsMks, f.FindNamedAt("product", "AndroidProducts.mk")...)
	err = dumpListToFile(ctx, config, androidProductsMks, filepath.Join(dumpDir, "AndroidProducts.mk.list"))
	if err != nil {
		ctx.Fatalf("Could not export product list: %v", err)
	}

	// Recursively look for all OWNERS files.
	owners := f.FindNamedAt(".", "OWNERS")
	err = dumpListToFile(ctx, config, owners, filepath.Join(dumpDir, "OWNERS.list"))
	if err != nil {
		ctx.Fatalf("Could not find OWNERS: %v", err)
	}

	// Recursively look for all METADATA files.
	metadataFiles := f.FindNamedAt(".", "METADATA")
	metadataFiles = ignoreNonAndroidMetadataFiles(metadataFiles)
	err = dumpListToFile(ctx, config, metadataFiles, filepath.Join(dumpDir, "METADATA.list"))
	if err != nil {
		ctx.Fatalf("Could not find METADATA: %v", err)
	}

	// Recursively look for all release_config_map.textproto files.
	releaseConfigMaps := f.FindNamedAt(".", "release_config_map.textproto")
	err = dumpListToFile(ctx, config, releaseConfigMaps, filepath.Join(dumpDir, "release_config_map.list"))
	if err != nil {
		ctx.Fatalf("Could not find release_config_map.textproto: %v", err)
	}

	// Recursively look for all duplicate_allowlist.txt files where we found release_config_map.textproto.
	var duplicateAllowlists []string
	for _, mapPath := range releaseConfigMaps {
		// The only `duplicate_allowlist.txt` files we care about are in the same directory
		// as `release_config_map.textproto`.
		duplicateAllowlists = append(duplicateAllowlists, f.FindNamedAt(filepath.Dir(mapPath), "duplicate_allowlist.txt")...)
	}
	err = dumpListToFile(ctx, config, duplicateAllowlists, filepath.Join(dumpDir, "duplicate_allowlist.list"))
	if err != nil {
		ctx.Fatalf("Could not find duplicate_allowlist.txt: %v", err)
	}

	// Recursively look for all TEST_MAPPING files.
	testMappings := f.FindNamedAt(".", "TEST_MAPPING")
	err = dumpListToFile(ctx, config, testMappings, filepath.Join(dumpDir, "TEST_MAPPING.list"))
	if err != nil {
		ctx.Fatalf("Could not find TEST_MAPPING: %v", err)
	}

	// Recursively look for all otatools cert files.
	otatools_cert_files := f.FindMatching("build/make/target/product/security", findOtaToolsCertFiles)
	otatools_cert_files = append(otatools_cert_files, f.FindMatching("device", findOtaToolsCertFiles)...)
	otatools_cert_files = append(otatools_cert_files, f.FindMatching("external/avb/test/data", findOtaToolsCertFiles)...)
	otatools_cert_files = append(otatools_cert_files, f.FindMatching("packages/modules", findOtaToolsCertFiles)...)
	otatools_cert_files = append(otatools_cert_files, f.FindMatching("vendor", findOtaToolsCertFiles)...)
	err = dumpListToFile(ctx, config, otatools_cert_files, filepath.Join(dumpDir, "OtaToolsCertFiles.list"))
	if err != nil {
		ctx.Fatalf("Could not find otatools cert files: %v", err)
	}

	// Recursively look for all Android.bp files
	androidBps := f.FindNamedAt(".", "Android.bp")
	if len(androidBps) == 0 {
		ctx.Fatalf("No Android.bp found")
	}
	err = dumpListToFile(ctx, config, androidBps, filepath.Join(dumpDir, "Android.bp.list"))
	if err != nil {
		ctx.Fatalf("Could not find modules: %v", err)
	}

	// Recursively look for all product/board config files.
	configurationFiles := f.FindMatching(".", findProductAndBoardConfigFiles)
	err = dumpListToFile(ctx, config, configurationFiles, filepath.Join(dumpDir, "configuration.list"))
	if err != nil {
		ctx.Fatalf("Could not export product/board configuration list: %v", err)
	}

	if config.Dist() {
		f.WaitForDbDump()
		// Dist the files.db plain text database.
		distFile(ctx, config, f.DbPath, "module_paths")
	}
}

// Write the .list files to disk.
func dumpListToFile(ctx Context, config Config, list []string, filePath string) (err error) {
	desiredText := strings.Join(list, "\n")
	desiredBytes := []byte(desiredText)
	actualBytes, readErr := ioutil.ReadFile(filePath)
	if readErr != nil || !bytes.Equal(desiredBytes, actualBytes) {
		err = ioutil.WriteFile(filePath, desiredBytes, 0777)
		if err != nil {
			return err
		}
	}

	distFile(ctx, config, filePath, "module_paths")

	return nil
}

func ignoreNonAndroidMetadataFiles(metadataFiles []string) []string {
	result := make([]string, 0, len(metadataFiles))
	for _, file := range metadataFiles {
		// Ignore files like prebuilts/clang/host/linux-x86/clang-r536225/python3/lib/python3.11/site-packages/pip-23.1.2.dist-info/METADATA
		// these METADATA files are from upstream and are not the METADATA files used in Android codebase.
		if strings.Contains(file, "prebuilts/clang/host/") && strings.Contains(file, "/site-packages/") {
			continue
		}
		result = append(result, file)
	}
	return result
}