• 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	"android/soong/ui/metrics"
19	"android/soong/ui/status"
20	"crypto/md5"
21	"fmt"
22	"io/ioutil"
23	"os"
24	"os/user"
25	"path/filepath"
26	"strings"
27)
28
29var spaceSlashReplacer = strings.NewReplacer("/", "_", " ", "_")
30
31const katiBuildSuffix = ""
32const katiCleanspecSuffix = "-cleanspec"
33const katiPackageSuffix = "-package"
34const katiSoongOnlyPackageSuffix = "-soong-only-package"
35
36// genKatiSuffix creates a filename suffix for kati-generated files so that we
37// can cache them based on their inputs. Such files include the generated Ninja
38// files and env.sh environment variable setup files.
39//
40// The filename suffix should encode all common changes to Kati inputs.
41// Currently that includes the TARGET_PRODUCT and kati-processed command line
42// arguments.
43func genKatiSuffix(ctx Context, config Config) {
44	targetProduct := "unknown"
45	if p, err := config.TargetProductOrErr(); err == nil {
46		targetProduct = p
47	}
48	// Construct the base suffix.
49	katiSuffix := "-" + targetProduct + config.CoverageSuffix()
50
51	// Append kati arguments to the suffix.
52	if args := config.KatiArgs(); len(args) > 0 {
53		katiSuffix += "-" + spaceSlashReplacer.Replace(strings.Join(args, "_"))
54	}
55
56	// If the suffix is too long, replace it with a md5 hash and write a
57	// file that contains the original suffix.
58	if len(katiSuffix) > 64 {
59		shortSuffix := "-" + fmt.Sprintf("%x", md5.Sum([]byte(katiSuffix)))
60		config.SetKatiSuffix(shortSuffix)
61
62		ctx.Verbosef("Kati ninja suffix too long: %q", katiSuffix)
63		ctx.Verbosef("Replacing with: %q", shortSuffix)
64
65		if err := ioutil.WriteFile(strings.TrimSuffix(config.KatiBuildNinjaFile(), "ninja")+"suf", []byte(katiSuffix), 0777); err != nil {
66			ctx.Println("Error writing suffix file:", err)
67		}
68	} else {
69		config.SetKatiSuffix(katiSuffix)
70	}
71}
72
73func writeValueIfChanged(ctx Context, config Config, dir string, filename string, value string) {
74	filePath := filepath.Join(dir, filename)
75	previousValue := ""
76	rawPreviousValue, err := os.ReadFile(filePath)
77	if err == nil {
78		previousValue = string(rawPreviousValue)
79	}
80
81	if previousValue != value {
82		if err = os.WriteFile(filePath, []byte(value), 0666); err != nil {
83			ctx.Fatalf("Failed to write: %v", err)
84		}
85	}
86}
87
88// Base function to construct and run the Kati command line with additional
89// arguments, and a custom function closure to mutate the environment Kati runs
90// in.
91func runKati(ctx Context, config Config, extraSuffix string, args []string, envFunc func(*Environment)) {
92	executable := config.KatiBin()
93	// cKati arguments.
94	args = append([]string{
95		// Instead of executing commands directly, generate a Ninja file.
96		"--ninja",
97		// Generate Ninja files in the output directory.
98		"--ninja_dir=" + config.OutDir(),
99		// Filename suffix of the generated Ninja file.
100		"--ninja_suffix=" + config.KatiSuffix() + extraSuffix,
101		// Remove common parts at the beginning of a Ninja file, like build_dir,
102		// local_pool and _kati_always_build_. Allows Kati to be run multiple
103		// times, with generated Ninja files combined in a single invocation
104		// using 'include'.
105		"--no_ninja_prelude",
106		// Support declaring phony outputs in AOSP Ninja.
107		"--use_ninja_phony_output",
108		// Regenerate the Ninja file if environment inputs have changed. e.g.
109		// CLI flags, .mk file timestamps, env vars, $(wildcard ..) and some
110		// $(shell ..) results.
111		"--regen",
112		// Skip '-include' directives starting with the specified path. Used to
113		// ignore generated .mk files.
114		"--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"),
115		// Detect the use of $(shell echo ...).
116		"--detect_android_echo",
117		// Colorful ANSI-based warning and error messages.
118		"--color_warnings",
119		// Generate all targets, not just the top level requested ones.
120		"--gen_all_targets",
121		// Use the built-in emulator of GNU find for better file finding
122		// performance. Used with $(shell find ...).
123		"--use_find_emulator",
124		// Fail when the find emulator encounters problems.
125		"--werror_find_emulator",
126		// Do not provide any built-in rules.
127		"--no_builtin_rules",
128		// Fail when suffix rules are used.
129		"--werror_suffix_rules",
130		// Fail when a real target depends on a phony target.
131		"--werror_real_to_phony",
132		// Makes real_to_phony checks assume that any top-level or leaf
133		// dependencies that does *not* have a '/' in it is a phony target.
134		"--top_level_phony",
135		// Fail when a phony target contains slashes.
136		"--werror_phony_looks_real",
137		// Fail when writing to a read-only directory.
138		"--werror_writable",
139		// Print Kati's internal statistics, such as the number of variables,
140		// implicit/explicit/suffix rules, and so on.
141		"--kati_stats",
142	}, args...)
143
144	// Generate a minimal Ninja file.
145	//
146	// Used for build_test and multiproduct_kati, which runs Kati several
147	// hundred times for different configurations to test file generation logic.
148	// These can result in generating Ninja files reaching ~1GB or more,
149	// resulting in ~hundreds of GBs of writes.
150	//
151	// Since we don't care about executing the Ninja files in these test cases,
152	// generating the Ninja file content wastes time, so skip writing any
153	// information out with --empty_ninja_file.
154	//
155	// From https://github.com/google/kati/commit/87b8da7af2c8bea28b1d8ab17679453d859f96e5
156	if config.EmptyNinjaFile() {
157		args = append(args, "--empty_ninja_file")
158	}
159
160	// Apply 'local_pool' to to all rules that don't specify a pool.
161	if config.UseRemoteBuild() {
162		args = append(args, "--default_pool=local_pool")
163	}
164
165	cmd := Command(ctx, config, "ckati", executable, args...)
166
167	// Set up the nsjail sandbox.
168	cmd.Sandbox = katiSandbox
169
170	// Set up stdout and stderr.
171	pipe, err := cmd.StdoutPipe()
172	if err != nil {
173		ctx.Fatalln("Error getting output pipe for ckati:", err)
174	}
175	cmd.Stderr = cmd.Stdout
176
177	var username string
178	// Pass on various build environment metadata to Kati.
179	if usernameFromEnv, ok := cmd.Environment.Get("BUILD_USERNAME"); !ok {
180		username = "unknown"
181		if u, err := user.Current(); err == nil {
182			username = u.Username
183		} else {
184			ctx.Println("Failed to get current user:", err)
185		}
186		cmd.Environment.Set("BUILD_USERNAME", username)
187	} else {
188		username = usernameFromEnv
189	}
190
191	// SOONG_USE_PARTIAL_COMPILE may be used in makefiles, but both cases must be supported.
192	//
193	// In general, the partial compile features will be implemented in Soong-based rules. We
194	// also allow them to be used in makefiles.  Clear the environment variable when calling
195	// kati so that we avoid reanalysis when the user changes it.  We will pass it to Ninja.
196	// As a result, rules where we want to allow the developer to toggle the feature ("use
197	// the partial compile feature" vs "legacy, aka full compile behavior") need to use this
198	// in the rule, since changing it will not cause reanalysis.
199	//
200	// Shell syntax in the rule might look something like this:
201	//     if [[ -n ${SOONG_USE_PARTIAL_COMPILE} ]]; then
202	//         # partial compile behavior
203	//     else
204	//         # legacy behavior
205	//     fi
206	cmd.Environment.Unset("SOONG_USE_PARTIAL_COMPILE")
207
208	// Unset BUILD_HOSTNAME during kati run to avoid kati rerun, kati will use BUILD_HOSTNAME from a file.
209	cmd.Environment.Unset("BUILD_HOSTNAME")
210
211	_, ok := cmd.Environment.Get("BUILD_NUMBER")
212	// Unset BUILD_NUMBER during kati run to avoid kati rerun, kati will use BUILD_NUMBER from a file.
213	cmd.Environment.Unset("BUILD_NUMBER")
214	if ok {
215		cmd.Environment.Set("HAS_BUILD_NUMBER", "true")
216	} else {
217		cmd.Environment.Set("HAS_BUILD_NUMBER", "false")
218	}
219
220	// Apply the caller's function closure to mutate the environment variables.
221	envFunc(cmd.Environment)
222
223	cmd.StartOrFatal()
224	// Set up the ToolStatus command line reader for Kati for a consistent UI
225	// for the user.
226	status.KatiReader(ctx.Status.StartTool(), pipe)
227	cmd.WaitOrFatal()
228}
229
230func runKatiBuild(ctx Context, config Config) {
231	ctx.BeginTrace(metrics.RunKati, "kati build")
232	defer ctx.EndTrace()
233
234	args := []string{
235		// Mark the output directory as writable.
236		"--writable", config.OutDir() + "/",
237		// Fail when encountering implicit rules. e.g.
238		// %.foo: %.bar
239		//   cp $< $@
240		"--werror_implicit_rules",
241		// Entry point for the Kati Ninja file generation.
242		"-f", "build/make/core/main.mk",
243	}
244
245	if !config.BuildBrokenDupRules() {
246		// Fail when redefining / duplicating a target.
247		args = append(args, "--werror_overriding_commands")
248	}
249
250	args = append(args, config.KatiArgs()...)
251
252	args = append(args,
253		// Location of the Make vars .mk file generated by Soong.
254		"SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk(),
255		// Location of the Android.mk file generated by Soong. This
256		// file contains Soong modules represented as Kati modules,
257		// allowing Kati modules to depend on Soong modules.
258		"SOONG_ANDROID_MK="+config.SoongAndroidMk(),
259		// Directory containing outputs for the target device.
260		"TARGET_DEVICE_DIR="+config.TargetDeviceDir(),
261		// Directory containing .mk files for packaging purposes, such as
262		// the dist.mk file, containing dist-for-goals data.
263		"KATI_PACKAGE_MK_DIR="+config.KatiPackageMkDir())
264
265	runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {})
266
267	// compress and dist the main build ninja file.
268	distGzipFile(ctx, config, config.KatiBuildNinjaFile())
269
270	// Cleanup steps.
271	cleanCopyHeaders(ctx, config)
272	cleanOldInstalledFiles(ctx, config)
273}
274
275// Clean out obsolete header files on the disk that were *not copied* during the
276// build with BUILD_COPY_HEADERS and LOCAL_COPY_HEADERS.
277//
278// These should be increasingly uncommon, as it's a deprecated feature and there
279// isn't an equivalent feature in Soong.
280func cleanCopyHeaders(ctx Context, config Config) {
281	ctx.BeginTrace("clean", "clean copy headers")
282	defer ctx.EndTrace()
283
284	// Read and parse the list of copied headers from a file in the product
285	// output directory.
286	data, err := ioutil.ReadFile(filepath.Join(config.ProductOut(), ".copied_headers_list"))
287	if err != nil {
288		if os.IsNotExist(err) {
289			return
290		}
291		ctx.Fatalf("Failed to read copied headers list: %v", err)
292	}
293
294	headers := strings.Fields(string(data))
295	if len(headers) < 1 {
296		ctx.Fatal("Failed to parse copied headers list: %q", string(data))
297	}
298	headerDir := headers[0]
299	headers = headers[1:]
300
301	// Walk the tree and remove any headers that are not in the list of copied
302	// headers in the current build.
303	filepath.Walk(headerDir,
304		func(path string, info os.FileInfo, err error) error {
305			if err != nil {
306				return nil
307			}
308			if info.IsDir() {
309				return nil
310			}
311			if !inList(path, headers) {
312				ctx.Printf("Removing obsolete header %q", path)
313				if err := os.Remove(path); err != nil {
314					ctx.Fatalf("Failed to remove obsolete header %q: %v", path, err)
315				}
316			}
317			return nil
318		})
319}
320
321// Clean out any previously installed files from the disk that are not installed
322// in the current build.
323func cleanOldInstalledFiles(ctx Context, config Config) {
324	ctx.BeginTrace("clean", "clean old installed files")
325	defer ctx.EndTrace()
326
327	// We shouldn't be removing files from one side of the two-step asan builds
328	var suffix string
329	if v, ok := config.Environment().Get("SANITIZE_TARGET"); ok {
330		if sanitize := strings.Fields(v); inList("address", sanitize) {
331			suffix = "_asan"
332		}
333	}
334
335	cleanOldFiles(ctx, config.ProductOut(), ".installable_files"+suffix)
336
337	cleanOldFiles(ctx, config.HostOut(), ".installable_test_files")
338}
339
340// Generate the Ninja file containing the packaging command lines for the dist
341// dir.
342func runKatiPackage(ctx Context, config Config, soongOnly bool) {
343	ctx.BeginTrace(metrics.RunKati, "kati package")
344	defer ctx.EndTrace()
345
346	entryPoint := "build/make/packaging/main.mk"
347	suffix := katiPackageSuffix
348	ninjaFile := config.KatiPackageNinjaFile()
349	if soongOnly {
350		entryPoint = "build/make/packaging/main_soong_only.mk"
351		suffix = katiSoongOnlyPackageSuffix
352		ninjaFile = config.KatiSoongOnlyPackageNinjaFile()
353	}
354
355	args := []string{
356		// Mark the dist dir as writable.
357		"--writable", config.DistDir() + "/",
358		// Fail when encountering implicit rules. e.g.
359		"--werror_implicit_rules",
360		// Fail when redefining / duplicating a target.
361		"--werror_overriding_commands",
362		// Entry point.
363		"-f", entryPoint,
364		// Directory containing .mk files for packaging purposes, such as
365		// the dist.mk file, containing dist-for-goals data.
366		"KATI_PACKAGE_MK_DIR=" + config.KatiPackageMkDir(),
367	}
368
369	// Run Kati against a restricted set of environment variables.
370	runKati(ctx, config, suffix, args, func(env *Environment) {
371		env.Allow([]string{
372			// Some generic basics
373			"LANG",
374			"LC_MESSAGES",
375			"PATH",
376			"PWD",
377			"TMPDIR",
378
379			// Tool configs
380			"ASAN_SYMBOLIZER_PATH",
381			"JAVA_HOME",
382			"PYTHONDONTWRITEBYTECODE",
383
384			// Build configuration
385			"ANDROID_BUILD_SHELL",
386			"DIST_DIR",
387			"OUT_DIR",
388			"FILE_NAME_TAG",
389		}...)
390
391		if config.Dist() {
392			env.Set("DIST", "true")
393			env.Set("DIST_DIR", config.DistDir())
394		}
395	})
396
397	// Compress and dist the packaging Ninja file.
398	distGzipFile(ctx, config, ninjaFile)
399}
400
401// Run Kati on the cleanspec files to clean the build.
402func runKatiCleanSpec(ctx Context, config Config) {
403	ctx.BeginTrace(metrics.RunKati, "kati cleanspec")
404	defer ctx.EndTrace()
405
406	runKati(ctx, config, katiCleanspecSuffix, []string{
407		// Fail when encountering implicit rules. e.g.
408		"--werror_implicit_rules",
409		// Fail when redefining / duplicating a target.
410		"--werror_overriding_commands",
411		// Entry point.
412		"-f", "build/make/core/cleanbuild.mk",
413		"SOONG_MAKEVARS_MK=" + config.SoongMakeVarsMk(),
414		"TARGET_DEVICE_DIR=" + config.TargetDeviceDir(),
415	}, func(env *Environment) {})
416}
417