// 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 ( "bufio" "crypto/md5" "fmt" "io" "io/ioutil" "path/filepath" "regexp" "strconv" "strings" ) var spaceSlashReplacer = strings.NewReplacer("/", "_", " ", "_") // 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 oneShot, ok := config.Environment().Get("ONE_SHOT_MAKEFILE"); ok { katiSuffix += "-" + spaceSlashReplacer.Replace(oneShot) } // 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.KatiNinjaFile(), "ninja")+"suf", []byte(katiSuffix), 0777); err != nil { ctx.Println("Error writing suffix file:", err) } } else { config.SetKatiSuffix(katiSuffix) } } func runKati(ctx Context, config Config) { genKatiSuffix(ctx, config) runKatiCleanSpec(ctx, config) ctx.BeginTrace("kati") defer ctx.EndTrace() executable := config.PrebuiltBuildTool("ckati") args := []string{ "--ninja", "--ninja_dir=" + config.OutDir(), "--ninja_suffix=" + config.KatiSuffix(), "--regen", "--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"), "--detect_android_echo", "--color_warnings", "--gen_all_targets", "--werror_find_emulator", "--kati_stats", "-f", "build/make/core/main.mk", } if !config.Environment().IsFalse("KATI_EMULATE_FIND") { args = append(args, "--use_find_emulator") } args = append(args, config.KatiArgs()...) args = append(args, "BUILDING_WITH_NINJA=true", "SOONG_ANDROID_MK="+config.SoongAndroidMk(), "SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk()) if config.UseGoma() { args = append(args, "-j"+strconv.Itoa(config.Parallel())) } 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 cmd.StartOrFatal() katiRewriteOutput(ctx, pipe) cmd.WaitOrFatal() } var katiIncludeRe = regexp.MustCompile(`^(\[\d+/\d+] )?including [^ ]+ ...$`) var katiLogRe = regexp.MustCompile(`^\*kati\*: `) func katiRewriteOutput(ctx Context, pipe io.ReadCloser) { haveBlankLine := true smartTerminal := ctx.IsTerminal() scanner := bufio.NewScanner(pipe) for scanner.Scan() { line := scanner.Text() verbose := katiIncludeRe.MatchString(line) // Only put kati debug/stat lines in our verbose log if katiLogRe.MatchString(line) { ctx.Verbose(line) continue } // For verbose lines, write them on the current line without a newline, // then overwrite them if the next thing we're printing is another // verbose line. if smartTerminal && verbose { // Limit line width to the terminal width, otherwise we'll wrap onto // another line and we won't delete the previous line. // // Run this on every line in case the window has been resized while // we're printing. This could be optimized to only re-run when we // get SIGWINCH if it ever becomes too time consuming. if max, ok := termWidth(ctx.Stdout()); ok { if len(line) > max { // Just do a max. Ninja elides the middle, but that's // more complicated and these lines aren't that important. line = line[:max] } } // Move to the beginning on the line, print the output, then clear // the rest of the line. fmt.Fprint(ctx.Stdout(), "\r", line, "\x1b[K") haveBlankLine = false continue } else if smartTerminal && !haveBlankLine { // If we've previously written a verbose message, send a newline to save // that message instead of overwriting it. fmt.Fprintln(ctx.Stdout()) haveBlankLine = true } else if !smartTerminal { // Most editors display these as garbage, so strip them out. line = string(stripAnsiEscapes([]byte(line))) } // Assume that non-verbose lines are important enough for stderr fmt.Fprintln(ctx.Stderr(), line) } // Save our last verbose line. if !haveBlankLine { fmt.Fprintln(ctx.Stdout()) } } func runKatiCleanSpec(ctx Context, config Config) { ctx.BeginTrace("kati cleanspec") defer ctx.EndTrace() executable := config.PrebuiltBuildTool("ckati") args := []string{ "--ninja", "--ninja_dir=" + config.OutDir(), "--ninja_suffix=" + config.KatiSuffix() + "-cleanspec", "--regen", "--detect_android_echo", "--color_warnings", "--gen_all_targets", "--werror_find_emulator", "--use_find_emulator", "-f", "build/make/core/cleanbuild.mk", "BUILDING_WITH_NINJA=true", "SOONG_MAKEVARS_MK=" + config.SoongMakeVarsMk(), } cmd := Command(ctx, config, "ckati", executable, args...) cmd.Sandbox = katiCleanSpecSandbox cmd.Stdout = ctx.Stdout() cmd.Stderr = ctx.Stderr() // Kati leaks memory, so ensure leak detection is turned off cmd.Environment.Set("ASAN_OPTIONS", "detect_leaks=0") cmd.RunOrFatal() }