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