// 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 ( "crypto/md5" "fmt" "io/ioutil" "os" "os/user" "path/filepath" "strings" "android/soong/ui/metrics" "android/soong/ui/status" ) var spaceSlashReplacer = strings.NewReplacer("/", "_", " ", "_") const katiBuildSuffix = "" const katiCleanspecSuffix = "-cleanspec" const katiPackageSuffix = "-package" // genKatiSuffix creates a suffix for kati-generated files so that we can cache // them based on their inputs. So this should encode all common changes to Kati // inputs. Currently that includes the TARGET_PRODUCT, kati-processed command // line arguments, and the directories specified by mm/mmm. func genKatiSuffix(ctx Context, config Config) { katiSuffix := "-" + config.TargetProduct() 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 runKati(ctx Context, config Config, extraSuffix string, args []string, envFunc func(*Environment)) { executable := config.PrebuiltBuildTool("ckati") args = append([]string{ "--ninja", "--ninja_dir=" + config.OutDir(), "--ninja_suffix=" + config.KatiSuffix() + extraSuffix, "--no_ninja_prelude", "--use_ninja_phony_output", "--regen", "--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"), "--detect_android_echo", "--color_warnings", "--gen_all_targets", "--use_find_emulator", "--werror_find_emulator", "--no_builtin_rules", "--werror_suffix_rules", "--warn_real_to_phony", "--warn_phony_looks_real", "--werror_real_to_phony", "--werror_phony_looks_real", "--werror_writable", "--top_level_phony", "--kati_stats", }, args...) if config.Environment().IsEnvTrue("EMPTY_NINJA_FILE") { args = append(args, "--empty_ninja_file") } if config.UseRemoteBuild() { args = append(args, "--default_pool=local_pool") } cmd := Command(ctx, config, "ckati", executable, args...) cmd.Sandbox = katiSandbox pipe, err := cmd.StdoutPipe() if err != nil { ctx.Fatalln("Error getting output pipe for ckati:", err) } cmd.Stderr = cmd.Stdout envFunc(cmd.Environment) if _, 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) } if _, ok := cmd.Environment.Get("BUILD_HOSTNAME"); !ok { hostname, err := os.Hostname() if err != nil { ctx.Println("Failed to read hostname:", err) hostname = "unknown" } cmd.Environment.Set("BUILD_HOSTNAME", hostname) } cmd.StartOrFatal() 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{ "--writable", config.OutDir() + "/", "-f", "build/make/core/main.mk", } // PDK builds still uses a few implicit rules if !config.IsPdkBuild() { args = append(args, "--werror_implicit_rules") } if !config.BuildBrokenDupRules() { args = append(args, "--werror_overriding_commands") } args = append(args, config.KatiArgs()...) args = append(args, "SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk(), "SOONG_ANDROID_MK="+config.SoongAndroidMk(), "TARGET_DEVICE_DIR="+config.TargetDeviceDir(), "KATI_PACKAGE_MK_DIR="+config.KatiPackageMkDir()) runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {}) distGzipFile(ctx, config, config.KatiBuildNinjaFile()) cleanCopyHeaders(ctx, config) cleanOldInstalledFiles(ctx, config) } func cleanCopyHeaders(ctx Context, config Config) { ctx.BeginTrace("clean", "clean copy headers") defer ctx.EndTrace() 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:] 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 }) } 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") } func runKatiPackage(ctx Context, config Config) { ctx.BeginTrace(metrics.RunKati, "kati package") defer ctx.EndTrace() args := []string{ "--writable", config.DistDir() + "/", "--werror_implicit_rules", "--werror_overriding_commands", "-f", "build/make/packaging/main.mk", "KATI_PACKAGE_MK_DIR=" + config.KatiPackageMkDir(), } 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", }...) if config.Dist() { env.Set("DIST", "true") env.Set("DIST_DIR", config.DistDir()) } }) distGzipFile(ctx, config, config.KatiPackageNinjaFile()) } func runKatiCleanSpec(ctx Context, config Config) { ctx.BeginTrace(metrics.RunKati, "kati cleanspec") defer ctx.EndTrace() runKati(ctx, config, katiCleanspecSuffix, []string{ "--werror_implicit_rules", "--werror_overriding_commands", "-f", "build/make/core/cleanbuild.mk", "SOONG_MAKEVARS_MK=" + config.SoongMakeVarsMk(), "TARGET_DEVICE_DIR=" + config.TargetDeviceDir(), }, func(env *Environment) {}) }