|  | // 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 ( | 
|  | "android/soong/ui/metrics" | 
|  | "android/soong/ui/status" | 
|  | "crypto/md5" | 
|  | "fmt" | 
|  | "io/ioutil" | 
|  | "os" | 
|  | "os/user" | 
|  | "path/filepath" | 
|  | "strings" | 
|  | ) | 
|  |  | 
|  | var spaceSlashReplacer = strings.NewReplacer("/", "_", " ", "_") | 
|  |  | 
|  | const katiBuildSuffix = "" | 
|  | const katiCleanspecSuffix = "-cleanspec" | 
|  | const katiPackageSuffix = "-package" | 
|  |  | 
|  | // genKatiSuffix creates a filename suffix for kati-generated files so that we | 
|  | // can cache them based on their inputs. Such files include the generated Ninja | 
|  | // files and env.sh environment variable setup files. | 
|  | // | 
|  | // The filename suffix should encode all common changes to Kati inputs. | 
|  | // Currently that includes the TARGET_PRODUCT and kati-processed command line | 
|  | // arguments. | 
|  | func genKatiSuffix(ctx Context, config Config) { | 
|  | // Construct the base suffix. | 
|  | katiSuffix := "-" + config.TargetProduct() | 
|  |  | 
|  | // Append kati arguments to the suffix. | 
|  | if args := config.KatiArgs(); len(args) > 0 { | 
|  | katiSuffix += "-" + spaceSlashReplacer.Replace(strings.Join(args, "_")) | 
|  | } | 
|  |  | 
|  | // If the suffix is too long, replace it with a md5 hash and write a | 
|  | // file that contains the original suffix. | 
|  | if len(katiSuffix) > 64 { | 
|  | shortSuffix := "-" + fmt.Sprintf("%x", md5.Sum([]byte(katiSuffix))) | 
|  | config.SetKatiSuffix(shortSuffix) | 
|  |  | 
|  | ctx.Verbosef("Kati ninja suffix too long: %q", katiSuffix) | 
|  | ctx.Verbosef("Replacing with: %q", shortSuffix) | 
|  |  | 
|  | if err := ioutil.WriteFile(strings.TrimSuffix(config.KatiBuildNinjaFile(), "ninja")+"suf", []byte(katiSuffix), 0777); err != nil { | 
|  | ctx.Println("Error writing suffix file:", err) | 
|  | } | 
|  | } else { | 
|  | config.SetKatiSuffix(katiSuffix) | 
|  | } | 
|  | } | 
|  |  | 
|  | func writeValueIfChanged(ctx Context, config Config, dir string, filename string, value string) { | 
|  | filePath := filepath.Join(dir, filename) | 
|  | previousValue := "" | 
|  | rawPreviousValue, err := ioutil.ReadFile(filePath) | 
|  | if err == nil { | 
|  | previousValue = string(rawPreviousValue) | 
|  | } | 
|  |  | 
|  | if previousValue != value { | 
|  | if err = ioutil.WriteFile(filePath, []byte(value), 0666); err != nil { | 
|  | ctx.Fatalf("Failed to write: %v", err) | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // Base function to construct and run the Kati command line with additional | 
|  | // arguments, and a custom function closure to mutate the environment Kati runs | 
|  | // in. | 
|  | func runKati(ctx Context, config Config, extraSuffix string, args []string, envFunc func(*Environment)) { | 
|  | executable := config.PrebuiltBuildTool("ckati") | 
|  | // cKati arguments. | 
|  | args = append([]string{ | 
|  | // Instead of executing commands directly, generate a Ninja file. | 
|  | "--ninja", | 
|  | // Generate Ninja files in the output directory. | 
|  | "--ninja_dir=" + config.OutDir(), | 
|  | // Filename suffix of the generated Ninja file. | 
|  | "--ninja_suffix=" + config.KatiSuffix() + extraSuffix, | 
|  | // Remove common parts at the beginning of a Ninja file, like build_dir, | 
|  | // local_pool and _kati_always_build_. Allows Kati to be run multiple | 
|  | // times, with generated Ninja files combined in a single invocation | 
|  | // using 'include'. | 
|  | "--no_ninja_prelude", | 
|  | // Support declaring phony outputs in AOSP Ninja. | 
|  | "--use_ninja_phony_output", | 
|  | // Regenerate the Ninja file if environment inputs have changed. e.g. | 
|  | // CLI flags, .mk file timestamps, env vars, $(wildcard ..) and some | 
|  | // $(shell ..) results. | 
|  | "--regen", | 
|  | // Skip '-include' directives starting with the specified path. Used to | 
|  | // ignore generated .mk files. | 
|  | "--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"), | 
|  | // Detect the use of $(shell echo ...). | 
|  | "--detect_android_echo", | 
|  | // Colorful ANSI-based warning and error messages. | 
|  | "--color_warnings", | 
|  | // Generate all targets, not just the top level requested ones. | 
|  | "--gen_all_targets", | 
|  | // Use the built-in emulator of GNU find for better file finding | 
|  | // performance. Used with $(shell find ...). | 
|  | "--use_find_emulator", | 
|  | // Fail when the find emulator encounters problems. | 
|  | "--werror_find_emulator", | 
|  | // Do not provide any built-in rules. | 
|  | "--no_builtin_rules", | 
|  | // Fail when suffix rules are used. | 
|  | "--werror_suffix_rules", | 
|  | // Fail when a real target depends on a phony target. | 
|  | "--werror_real_to_phony", | 
|  | // Makes real_to_phony checks assume that any top-level or leaf | 
|  | // dependencies that does *not* have a '/' in it is a phony target. | 
|  | "--top_level_phony", | 
|  | // Fail when a phony target contains slashes. | 
|  | "--werror_phony_looks_real", | 
|  | // Fail when writing to a read-only directory. | 
|  | "--werror_writable", | 
|  | // Print Kati's internal statistics, such as the number of variables, | 
|  | // implicit/explicit/suffix rules, and so on. | 
|  | "--kati_stats", | 
|  | }, args...) | 
|  |  | 
|  | // Generate a minimal Ninja file. | 
|  | // | 
|  | // Used for build_test and multiproduct_kati, which runs Kati several | 
|  | // hundred times for different configurations to test file generation logic. | 
|  | // These can result in generating Ninja files reaching ~1GB or more, | 
|  | // resulting in ~hundreds of GBs of writes. | 
|  | // | 
|  | // Since we don't care about executing the Ninja files in these test cases, | 
|  | // generating the Ninja file content wastes time, so skip writing any | 
|  | // information out with --empty_ninja_file. | 
|  | // | 
|  | // From https://github.com/google/kati/commit/87b8da7af2c8bea28b1d8ab17679453d859f96e5 | 
|  | if config.EmptyNinjaFile() { | 
|  | args = append(args, "--empty_ninja_file") | 
|  | } | 
|  |  | 
|  | // Apply 'local_pool' to to all rules that don't specify a pool. | 
|  | if config.UseRemoteBuild() { | 
|  | args = append(args, "--default_pool=local_pool") | 
|  | } | 
|  |  | 
|  | cmd := Command(ctx, config, "ckati", executable, args...) | 
|  |  | 
|  | // Set up the nsjail sandbox. | 
|  | cmd.Sandbox = katiSandbox | 
|  |  | 
|  | // Set up stdout and stderr. | 
|  | pipe, err := cmd.StdoutPipe() | 
|  | if err != nil { | 
|  | ctx.Fatalln("Error getting output pipe for ckati:", err) | 
|  | } | 
|  | cmd.Stderr = cmd.Stdout | 
|  |  | 
|  | var username string | 
|  | // Pass on various build environment metadata to Kati. | 
|  | if usernameFromEnv, ok := cmd.Environment.Get("BUILD_USERNAME"); !ok { | 
|  | username = "unknown" | 
|  | if u, err := user.Current(); err == nil { | 
|  | username = u.Username | 
|  | } else { | 
|  | ctx.Println("Failed to get current user:", err) | 
|  | } | 
|  | cmd.Environment.Set("BUILD_USERNAME", username) | 
|  | } else { | 
|  | username = usernameFromEnv | 
|  | } | 
|  |  | 
|  | hostname, ok := cmd.Environment.Get("BUILD_HOSTNAME") | 
|  | // Unset BUILD_HOSTNAME during kati run to avoid kati rerun, kati will use BUILD_HOSTNAME from a file. | 
|  | cmd.Environment.Unset("BUILD_HOSTNAME") | 
|  | if !ok { | 
|  | hostname, err = os.Hostname() | 
|  | if err != nil { | 
|  | ctx.Println("Failed to read hostname:", err) | 
|  | hostname = "unknown" | 
|  | } | 
|  | } | 
|  | writeValueIfChanged(ctx, config, config.SoongOutDir(), "build_hostname.txt", hostname) | 
|  | _, ok = cmd.Environment.Get("BUILD_NUMBER") | 
|  | // Unset BUILD_NUMBER during kati run to avoid kati rerun, kati will use BUILD_NUMBER from a file. | 
|  | cmd.Environment.Unset("BUILD_NUMBER") | 
|  | if ok { | 
|  | cmd.Environment.Set("HAS_BUILD_NUMBER", "true") | 
|  | } else { | 
|  | cmd.Environment.Set("HAS_BUILD_NUMBER", "false") | 
|  | } | 
|  |  | 
|  | // Apply the caller's function closure to mutate the environment variables. | 
|  | envFunc(cmd.Environment) | 
|  |  | 
|  | cmd.StartOrFatal() | 
|  | // Set up the ToolStatus command line reader for Kati for a consistent UI | 
|  | // for the user. | 
|  | status.KatiReader(ctx.Status.StartTool(), pipe) | 
|  | cmd.WaitOrFatal() | 
|  | } | 
|  |  | 
|  | func runKatiBuild(ctx Context, config Config) { | 
|  | ctx.BeginTrace(metrics.RunKati, "kati build") | 
|  | defer ctx.EndTrace() | 
|  |  | 
|  | args := []string{ | 
|  | // Mark the output directory as writable. | 
|  | "--writable", config.OutDir() + "/", | 
|  | // Fail when encountering implicit rules. e.g. | 
|  | // %.foo: %.bar | 
|  | //   cp $< $@ | 
|  | "--werror_implicit_rules", | 
|  | // Entry point for the Kati Ninja file generation. | 
|  | "-f", "build/make/core/main.mk", | 
|  | } | 
|  |  | 
|  | if !config.BuildBrokenDupRules() { | 
|  | // Fail when redefining / duplicating a target. | 
|  | args = append(args, "--werror_overriding_commands") | 
|  | } | 
|  |  | 
|  | args = append(args, config.KatiArgs()...) | 
|  |  | 
|  | args = append(args, | 
|  | // Location of the Make vars .mk file generated by Soong. | 
|  | "SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk(), | 
|  | // Location of the Android.mk file generated by Soong. This | 
|  | // file contains Soong modules represented as Kati modules, | 
|  | // allowing Kati modules to depend on Soong modules. | 
|  | "SOONG_ANDROID_MK="+config.SoongAndroidMk(), | 
|  | // Directory containing outputs for the target device. | 
|  | "TARGET_DEVICE_DIR="+config.TargetDeviceDir(), | 
|  | // Directory containing .mk files for packaging purposes, such as | 
|  | // the dist.mk file, containing dist-for-goals data. | 
|  | "KATI_PACKAGE_MK_DIR="+config.KatiPackageMkDir()) | 
|  |  | 
|  | runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {}) | 
|  |  | 
|  | // compress and dist the main build ninja file. | 
|  | distGzipFile(ctx, config, config.KatiBuildNinjaFile()) | 
|  |  | 
|  | // Cleanup steps. | 
|  | cleanCopyHeaders(ctx, config) | 
|  | cleanOldInstalledFiles(ctx, config) | 
|  | } | 
|  |  | 
|  | // Clean out obsolete header files on the disk that were *not copied* during the | 
|  | // build with BUILD_COPY_HEADERS and LOCAL_COPY_HEADERS. | 
|  | // | 
|  | // These should be increasingly uncommon, as it's a deprecated feature and there | 
|  | // isn't an equivalent feature in Soong. | 
|  | func cleanCopyHeaders(ctx Context, config Config) { | 
|  | ctx.BeginTrace("clean", "clean copy headers") | 
|  | defer ctx.EndTrace() | 
|  |  | 
|  | // Read and parse the list of copied headers from a file in the product | 
|  | // output directory. | 
|  | data, err := ioutil.ReadFile(filepath.Join(config.ProductOut(), ".copied_headers_list")) | 
|  | if err != nil { | 
|  | if os.IsNotExist(err) { | 
|  | return | 
|  | } | 
|  | ctx.Fatalf("Failed to read copied headers list: %v", err) | 
|  | } | 
|  |  | 
|  | headers := strings.Fields(string(data)) | 
|  | if len(headers) < 1 { | 
|  | ctx.Fatal("Failed to parse copied headers list: %q", string(data)) | 
|  | } | 
|  | headerDir := headers[0] | 
|  | headers = headers[1:] | 
|  |  | 
|  | // Walk the tree and remove any headers that are not in the list of copied | 
|  | // headers in the current build. | 
|  | filepath.Walk(headerDir, | 
|  | func(path string, info os.FileInfo, err error) error { | 
|  | if err != nil { | 
|  | return nil | 
|  | } | 
|  | if info.IsDir() { | 
|  | return nil | 
|  | } | 
|  | if !inList(path, headers) { | 
|  | ctx.Printf("Removing obsolete header %q", path) | 
|  | if err := os.Remove(path); err != nil { | 
|  | ctx.Fatalf("Failed to remove obsolete header %q: %v", path, err) | 
|  | } | 
|  | } | 
|  | return nil | 
|  | }) | 
|  | } | 
|  |  | 
|  | // Clean out any previously installed files from the disk that are not installed | 
|  | // in the current build. | 
|  | func cleanOldInstalledFiles(ctx Context, config Config) { | 
|  | ctx.BeginTrace("clean", "clean old installed files") | 
|  | defer ctx.EndTrace() | 
|  |  | 
|  | // We shouldn't be removing files from one side of the two-step asan builds | 
|  | var suffix string | 
|  | if v, ok := config.Environment().Get("SANITIZE_TARGET"); ok { | 
|  | if sanitize := strings.Fields(v); inList("address", sanitize) { | 
|  | suffix = "_asan" | 
|  | } | 
|  | } | 
|  |  | 
|  | cleanOldFiles(ctx, config.ProductOut(), ".installable_files"+suffix) | 
|  |  | 
|  | cleanOldFiles(ctx, config.HostOut(), ".installable_test_files") | 
|  | } | 
|  |  | 
|  | // Generate the Ninja file containing the packaging command lines for the dist | 
|  | // dir. | 
|  | func runKatiPackage(ctx Context, config Config) { | 
|  | ctx.BeginTrace(metrics.RunKati, "kati package") | 
|  | defer ctx.EndTrace() | 
|  |  | 
|  | args := []string{ | 
|  | // Mark the dist dir as writable. | 
|  | "--writable", config.DistDir() + "/", | 
|  | // Fail when encountering implicit rules. e.g. | 
|  | "--werror_implicit_rules", | 
|  | // Fail when redefining / duplicating a target. | 
|  | "--werror_overriding_commands", | 
|  | // Entry point. | 
|  | "-f", "build/make/packaging/main.mk", | 
|  | // Directory containing .mk files for packaging purposes, such as | 
|  | // the dist.mk file, containing dist-for-goals data. | 
|  | "KATI_PACKAGE_MK_DIR=" + config.KatiPackageMkDir(), | 
|  | } | 
|  |  | 
|  | // Run Kati against a restricted set of environment variables. | 
|  | runKati(ctx, config, katiPackageSuffix, args, func(env *Environment) { | 
|  | env.Allow([]string{ | 
|  | // Some generic basics | 
|  | "LANG", | 
|  | "LC_MESSAGES", | 
|  | "PATH", | 
|  | "PWD", | 
|  | "TMPDIR", | 
|  |  | 
|  | // Tool configs | 
|  | "ASAN_SYMBOLIZER_PATH", | 
|  | "JAVA_HOME", | 
|  | "PYTHONDONTWRITEBYTECODE", | 
|  |  | 
|  | // Build configuration | 
|  | "ANDROID_BUILD_SHELL", | 
|  | "DIST_DIR", | 
|  | "OUT_DIR", | 
|  | "FILE_NAME_TAG", | 
|  | }...) | 
|  |  | 
|  | if config.Dist() { | 
|  | env.Set("DIST", "true") | 
|  | env.Set("DIST_DIR", config.DistDir()) | 
|  | } | 
|  | }) | 
|  |  | 
|  | // Compress and dist the packaging Ninja file. | 
|  | distGzipFile(ctx, config, config.KatiPackageNinjaFile()) | 
|  | } | 
|  |  | 
|  | // Run Kati on the cleanspec files to clean the build. | 
|  | func runKatiCleanSpec(ctx Context, config Config) { | 
|  | ctx.BeginTrace(metrics.RunKati, "kati cleanspec") | 
|  | defer ctx.EndTrace() | 
|  |  | 
|  | runKati(ctx, config, katiCleanspecSuffix, []string{ | 
|  | // Fail when encountering implicit rules. e.g. | 
|  | "--werror_implicit_rules", | 
|  | // Fail when redefining / duplicating a target. | 
|  | "--werror_overriding_commands", | 
|  | // Entry point. | 
|  | "-f", "build/make/core/cleanbuild.mk", | 
|  | "SOONG_MAKEVARS_MK=" + config.SoongMakeVarsMk(), | 
|  | "TARGET_DEVICE_DIR=" + config.TargetDeviceDir(), | 
|  | }, func(env *Environment) {}) | 
|  | } |