Home | History | Annotate | Download | only in build
      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 
     15 package build
     16 
     17 import (
     18 	"bufio"
     19 	"crypto/md5"
     20 	"fmt"
     21 	"io"
     22 	"io/ioutil"
     23 	"path/filepath"
     24 	"regexp"
     25 	"strconv"
     26 	"strings"
     27 )
     28 
     29 var spaceSlashReplacer = strings.NewReplacer("/", "_", " ", "_")
     30 
     31 // genKatiSuffix creates a suffix for kati-generated files so that we can cache
     32 // them based on their inputs. So this should encode all common changes to Kati
     33 // inputs. Currently that includes the TARGET_PRODUCT, kati-processed command
     34 // line arguments, and the directories specified by mm/mmm.
     35 func genKatiSuffix(ctx Context, config Config) {
     36 	katiSuffix := "-" + config.TargetProduct()
     37 	if args := config.KatiArgs(); len(args) > 0 {
     38 		katiSuffix += "-" + spaceSlashReplacer.Replace(strings.Join(args, "_"))
     39 	}
     40 	if oneShot, ok := config.Environment().Get("ONE_SHOT_MAKEFILE"); ok {
     41 		katiSuffix += "-" + spaceSlashReplacer.Replace(oneShot)
     42 	}
     43 
     44 	// If the suffix is too long, replace it with a md5 hash and write a
     45 	// file that contains the original suffix.
     46 	if len(katiSuffix) > 64 {
     47 		shortSuffix := "-" + fmt.Sprintf("%x", md5.Sum([]byte(katiSuffix)))
     48 		config.SetKatiSuffix(shortSuffix)
     49 
     50 		ctx.Verbosef("Kati ninja suffix too long: %q", katiSuffix)
     51 		ctx.Verbosef("Replacing with: %q", shortSuffix)
     52 
     53 		if err := ioutil.WriteFile(strings.TrimSuffix(config.KatiNinjaFile(), "ninja")+"suf", []byte(katiSuffix), 0777); err != nil {
     54 			ctx.Println("Error writing suffix file:", err)
     55 		}
     56 	} else {
     57 		config.SetKatiSuffix(katiSuffix)
     58 	}
     59 }
     60 
     61 func runKati(ctx Context, config Config) {
     62 	genKatiSuffix(ctx, config)
     63 
     64 	runKatiCleanSpec(ctx, config)
     65 
     66 	ctx.BeginTrace("kati")
     67 	defer ctx.EndTrace()
     68 
     69 	executable := config.PrebuiltBuildTool("ckati")
     70 	args := []string{
     71 		"--ninja",
     72 		"--ninja_dir=" + config.OutDir(),
     73 		"--ninja_suffix=" + config.KatiSuffix(),
     74 		"--regen",
     75 		"--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"),
     76 		"--detect_android_echo",
     77 		"--color_warnings",
     78 		"--gen_all_targets",
     79 		"--werror_find_emulator",
     80 		"--kati_stats",
     81 		"-f", "build/make/core/main.mk",
     82 	}
     83 
     84 	if !config.Environment().IsFalse("KATI_EMULATE_FIND") {
     85 		args = append(args, "--use_find_emulator")
     86 	}
     87 
     88 	args = append(args, config.KatiArgs()...)
     89 
     90 	args = append(args,
     91 		"BUILDING_WITH_NINJA=true",
     92 		"SOONG_ANDROID_MK="+config.SoongAndroidMk(),
     93 		"SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk())
     94 
     95 	if config.UseGoma() {
     96 		args = append(args, "-j"+strconv.Itoa(config.Parallel()))
     97 	}
     98 
     99 	cmd := Command(ctx, config, "ckati", executable, args...)
    100 	cmd.Sandbox = katiSandbox
    101 	pipe, err := cmd.StdoutPipe()
    102 	if err != nil {
    103 		ctx.Fatalln("Error getting output pipe for ckati:", err)
    104 	}
    105 	cmd.Stderr = cmd.Stdout
    106 
    107 	cmd.StartOrFatal()
    108 	katiRewriteOutput(ctx, pipe)
    109 	cmd.WaitOrFatal()
    110 }
    111 
    112 var katiIncludeRe = regexp.MustCompile(`^(\[\d+/\d+] )?including [^ ]+ ...$`)
    113 var katiLogRe = regexp.MustCompile(`^\*kati\*: `)
    114 
    115 func katiRewriteOutput(ctx Context, pipe io.ReadCloser) {
    116 	haveBlankLine := true
    117 	smartTerminal := ctx.IsTerminal()
    118 	errSmartTerminal := ctx.IsErrTerminal()
    119 
    120 	scanner := bufio.NewScanner(pipe)
    121 	for scanner.Scan() {
    122 		line := scanner.Text()
    123 		verbose := katiIncludeRe.MatchString(line)
    124 
    125 		// Only put kati debug/stat lines in our verbose log
    126 		if katiLogRe.MatchString(line) {
    127 			ctx.Verbose(line)
    128 			continue
    129 		}
    130 
    131 		// For verbose lines, write them on the current line without a newline,
    132 		// then overwrite them if the next thing we're printing is another
    133 		// verbose line.
    134 		if smartTerminal && verbose {
    135 			// Limit line width to the terminal width, otherwise we'll wrap onto
    136 			// another line and we won't delete the previous line.
    137 			//
    138 			// Run this on every line in case the window has been resized while
    139 			// we're printing. This could be optimized to only re-run when we
    140 			// get SIGWINCH if it ever becomes too time consuming.
    141 			if max, ok := termWidth(ctx.Stdout()); ok {
    142 				if len(line) > max {
    143 					// Just do a max. Ninja elides the middle, but that's
    144 					// more complicated and these lines aren't that important.
    145 					line = line[:max]
    146 				}
    147 			}
    148 
    149 			// Move to the beginning on the line, print the output, then clear
    150 			// the rest of the line.
    151 			fmt.Fprint(ctx.Stdout(), "\r", line, "\x1b[K")
    152 			haveBlankLine = false
    153 			continue
    154 		} else if smartTerminal && !haveBlankLine {
    155 			// If we've previously written a verbose message, send a newline to save
    156 			// that message instead of overwriting it.
    157 			fmt.Fprintln(ctx.Stdout())
    158 			haveBlankLine = true
    159 		} else if !errSmartTerminal {
    160 			// Most editors display these as garbage, so strip them out.
    161 			line = string(stripAnsiEscapes([]byte(line)))
    162 		}
    163 
    164 		// Assume that non-verbose lines are important enough for stderr
    165 		fmt.Fprintln(ctx.Stderr(), line)
    166 	}
    167 
    168 	// Save our last verbose line.
    169 	if !haveBlankLine {
    170 		fmt.Fprintln(ctx.Stdout())
    171 	}
    172 }
    173 
    174 func runKatiCleanSpec(ctx Context, config Config) {
    175 	ctx.BeginTrace("kati cleanspec")
    176 	defer ctx.EndTrace()
    177 
    178 	executable := config.PrebuiltBuildTool("ckati")
    179 	args := []string{
    180 		"--ninja",
    181 		"--ninja_dir=" + config.OutDir(),
    182 		"--ninja_suffix=" + config.KatiSuffix() + "-cleanspec",
    183 		"--regen",
    184 		"--detect_android_echo",
    185 		"--color_warnings",
    186 		"--gen_all_targets",
    187 		"--werror_find_emulator",
    188 		"--use_find_emulator",
    189 		"-f", "build/make/core/cleanbuild.mk",
    190 		"BUILDING_WITH_NINJA=true",
    191 		"SOONG_MAKEVARS_MK=" + config.SoongMakeVarsMk(),
    192 	}
    193 
    194 	cmd := Command(ctx, config, "ckati", executable, args...)
    195 	cmd.Sandbox = katiCleanSpecSandbox
    196 	cmd.Stdout = ctx.Stdout()
    197 	cmd.Stderr = ctx.Stderr()
    198 
    199 	// Kati leaks memory, so ensure leak detection is turned off
    200 	cmd.Environment.Set("ASAN_OPTIONS", "detect_leaks=0")
    201 	cmd.RunOrFatal()
    202 }
    203