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 15package build 16 17import ( 18 "crypto/md5" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "os/user" 23 "path/filepath" 24 "strings" 25 26 "android/soong/ui/metrics" 27 "android/soong/ui/status" 28) 29 30var spaceSlashReplacer = strings.NewReplacer("/", "_", " ", "_") 31 32const katiBuildSuffix = "" 33const katiCleanspecSuffix = "-cleanspec" 34const katiPackageSuffix = "-package" 35 36// genKatiSuffix creates a suffix for kati-generated files so that we can cache 37// them based on their inputs. So this should encode all common changes to Kati 38// inputs. Currently that includes the TARGET_PRODUCT, kati-processed command 39// line arguments, and the directories specified by mm/mmm. 40func genKatiSuffix(ctx Context, config Config) { 41 katiSuffix := "-" + config.TargetProduct() 42 if args := config.KatiArgs(); len(args) > 0 { 43 katiSuffix += "-" + spaceSlashReplacer.Replace(strings.Join(args, "_")) 44 } 45 46 // If the suffix is too long, replace it with a md5 hash and write a 47 // file that contains the original suffix. 48 if len(katiSuffix) > 64 { 49 shortSuffix := "-" + fmt.Sprintf("%x", md5.Sum([]byte(katiSuffix))) 50 config.SetKatiSuffix(shortSuffix) 51 52 ctx.Verbosef("Kati ninja suffix too long: %q", katiSuffix) 53 ctx.Verbosef("Replacing with: %q", shortSuffix) 54 55 if err := ioutil.WriteFile(strings.TrimSuffix(config.KatiBuildNinjaFile(), "ninja")+"suf", []byte(katiSuffix), 0777); err != nil { 56 ctx.Println("Error writing suffix file:", err) 57 } 58 } else { 59 config.SetKatiSuffix(katiSuffix) 60 } 61} 62 63func runKati(ctx Context, config Config, extraSuffix string, args []string, envFunc func(*Environment)) { 64 executable := config.PrebuiltBuildTool("ckati") 65 args = append([]string{ 66 "--ninja", 67 "--ninja_dir=" + config.OutDir(), 68 "--ninja_suffix=" + config.KatiSuffix() + extraSuffix, 69 "--no_ninja_prelude", 70 "--use_ninja_phony_output", 71 "--regen", 72 "--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"), 73 "--detect_android_echo", 74 "--color_warnings", 75 "--gen_all_targets", 76 "--use_find_emulator", 77 "--werror_find_emulator", 78 "--no_builtin_rules", 79 "--werror_suffix_rules", 80 "--warn_real_to_phony", 81 "--warn_phony_looks_real", 82 "--werror_real_to_phony", 83 "--werror_phony_looks_real", 84 "--werror_writable", 85 "--top_level_phony", 86 "--kati_stats", 87 }, args...) 88 89 if config.Environment().IsEnvTrue("EMPTY_NINJA_FILE") { 90 args = append(args, "--empty_ninja_file") 91 } 92 93 if config.UseRemoteBuild() { 94 args = append(args, "--default_pool=local_pool") 95 } 96 97 cmd := Command(ctx, config, "ckati", executable, args...) 98 cmd.Sandbox = katiSandbox 99 pipe, err := cmd.StdoutPipe() 100 if err != nil { 101 ctx.Fatalln("Error getting output pipe for ckati:", err) 102 } 103 cmd.Stderr = cmd.Stdout 104 105 envFunc(cmd.Environment) 106 107 if _, ok := cmd.Environment.Get("BUILD_USERNAME"); !ok { 108 username := "unknown" 109 if u, err := user.Current(); err == nil { 110 username = u.Username 111 } else { 112 ctx.Println("Failed to get current user:", err) 113 } 114 cmd.Environment.Set("BUILD_USERNAME", username) 115 } 116 117 if _, ok := cmd.Environment.Get("BUILD_HOSTNAME"); !ok { 118 hostname, err := os.Hostname() 119 if err != nil { 120 ctx.Println("Failed to read hostname:", err) 121 hostname = "unknown" 122 } 123 cmd.Environment.Set("BUILD_HOSTNAME", hostname) 124 } 125 126 cmd.StartOrFatal() 127 status.KatiReader(ctx.Status.StartTool(), pipe) 128 cmd.WaitOrFatal() 129} 130 131func runKatiBuild(ctx Context, config Config) { 132 ctx.BeginTrace(metrics.RunKati, "kati build") 133 defer ctx.EndTrace() 134 135 args := []string{ 136 "--writable", config.OutDir() + "/", 137 "-f", "build/make/core/main.mk", 138 } 139 140 // PDK builds still uses a few implicit rules 141 if !config.IsPdkBuild() { 142 args = append(args, "--werror_implicit_rules") 143 } 144 145 if !config.BuildBrokenDupRules() { 146 args = append(args, "--werror_overriding_commands") 147 } 148 149 args = append(args, config.KatiArgs()...) 150 151 args = append(args, 152 "SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk(), 153 "SOONG_ANDROID_MK="+config.SoongAndroidMk(), 154 "TARGET_DEVICE_DIR="+config.TargetDeviceDir(), 155 "KATI_PACKAGE_MK_DIR="+config.KatiPackageMkDir()) 156 157 runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {}) 158 159 distGzipFile(ctx, config, config.KatiBuildNinjaFile()) 160 161 cleanCopyHeaders(ctx, config) 162 cleanOldInstalledFiles(ctx, config) 163} 164 165func cleanCopyHeaders(ctx Context, config Config) { 166 ctx.BeginTrace("clean", "clean copy headers") 167 defer ctx.EndTrace() 168 169 data, err := ioutil.ReadFile(filepath.Join(config.ProductOut(), ".copied_headers_list")) 170 if err != nil { 171 if os.IsNotExist(err) { 172 return 173 } 174 ctx.Fatalf("Failed to read copied headers list: %v", err) 175 } 176 177 headers := strings.Fields(string(data)) 178 if len(headers) < 1 { 179 ctx.Fatal("Failed to parse copied headers list: %q", string(data)) 180 } 181 headerDir := headers[0] 182 headers = headers[1:] 183 184 filepath.Walk(headerDir, 185 func(path string, info os.FileInfo, err error) error { 186 if err != nil { 187 return nil 188 } 189 if info.IsDir() { 190 return nil 191 } 192 if !inList(path, headers) { 193 ctx.Printf("Removing obsolete header %q", path) 194 if err := os.Remove(path); err != nil { 195 ctx.Fatalf("Failed to remove obsolete header %q: %v", path, err) 196 } 197 } 198 return nil 199 }) 200} 201 202func cleanOldInstalledFiles(ctx Context, config Config) { 203 ctx.BeginTrace("clean", "clean old installed files") 204 defer ctx.EndTrace() 205 206 // We shouldn't be removing files from one side of the two-step asan builds 207 var suffix string 208 if v, ok := config.Environment().Get("SANITIZE_TARGET"); ok { 209 if sanitize := strings.Fields(v); inList("address", sanitize) { 210 suffix = "_asan" 211 } 212 } 213 214 cleanOldFiles(ctx, config.ProductOut(), ".installable_files"+suffix) 215 216 cleanOldFiles(ctx, config.HostOut(), ".installable_test_files") 217} 218 219func runKatiPackage(ctx Context, config Config) { 220 ctx.BeginTrace(metrics.RunKati, "kati package") 221 defer ctx.EndTrace() 222 223 args := []string{ 224 "--writable", config.DistDir() + "/", 225 "--werror_implicit_rules", 226 "--werror_overriding_commands", 227 "-f", "build/make/packaging/main.mk", 228 "KATI_PACKAGE_MK_DIR=" + config.KatiPackageMkDir(), 229 } 230 231 runKati(ctx, config, katiPackageSuffix, args, func(env *Environment) { 232 env.Allow([]string{ 233 // Some generic basics 234 "LANG", 235 "LC_MESSAGES", 236 "PATH", 237 "PWD", 238 "TMPDIR", 239 240 // Tool configs 241 "ASAN_SYMBOLIZER_PATH", 242 "JAVA_HOME", 243 "PYTHONDONTWRITEBYTECODE", 244 245 // Build configuration 246 "ANDROID_BUILD_SHELL", 247 "DIST_DIR", 248 "OUT_DIR", 249 }...) 250 251 if config.Dist() { 252 env.Set("DIST", "true") 253 env.Set("DIST_DIR", config.DistDir()) 254 } 255 }) 256 257 distGzipFile(ctx, config, config.KatiPackageNinjaFile()) 258} 259 260func runKatiCleanSpec(ctx Context, config Config) { 261 ctx.BeginTrace(metrics.RunKati, "kati cleanspec") 262 defer ctx.EndTrace() 263 264 runKati(ctx, config, katiCleanspecSuffix, []string{ 265 "--werror_implicit_rules", 266 "--werror_overriding_commands", 267 "-f", "build/make/core/cleanbuild.mk", 268 "SOONG_MAKEVARS_MK=" + config.SoongMakeVarsMk(), 269 "TARGET_DEVICE_DIR=" + config.TargetDeviceDir(), 270 }, func(env *Environment) {}) 271} 272