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 filename suffix for kati-generated files so that we 37// can cache them based on their inputs. Such files include the generated Ninja 38// files and env.sh environment variable setup files. 39// 40// The filename suffix should encode all common changes to Kati inputs. 41// Currently that includes the TARGET_PRODUCT and kati-processed command line 42// arguments. 43func genKatiSuffix(ctx Context, config Config) { 44 // Construct the base suffix. 45 katiSuffix := "-" + config.TargetProduct() 46 47 // Append kati arguments to the suffix. 48 if args := config.KatiArgs(); len(args) > 0 { 49 katiSuffix += "-" + spaceSlashReplacer.Replace(strings.Join(args, "_")) 50 } 51 52 // If the suffix is too long, replace it with a md5 hash and write a 53 // file that contains the original suffix. 54 if len(katiSuffix) > 64 { 55 shortSuffix := "-" + fmt.Sprintf("%x", md5.Sum([]byte(katiSuffix))) 56 config.SetKatiSuffix(shortSuffix) 57 58 ctx.Verbosef("Kati ninja suffix too long: %q", katiSuffix) 59 ctx.Verbosef("Replacing with: %q", shortSuffix) 60 61 if err := ioutil.WriteFile(strings.TrimSuffix(config.KatiBuildNinjaFile(), "ninja")+"suf", []byte(katiSuffix), 0777); err != nil { 62 ctx.Println("Error writing suffix file:", err) 63 } 64 } else { 65 config.SetKatiSuffix(katiSuffix) 66 } 67} 68 69// Base function to construct and run the Kati command line with additional 70// arguments, and a custom function closure to mutate the environment Kati runs 71// in. 72func runKati(ctx Context, config Config, extraSuffix string, args []string, envFunc func(*Environment)) { 73 executable := config.PrebuiltBuildTool("ckati") 74 // cKati arguments. 75 args = append([]string{ 76 // Instead of executing commands directly, generate a Ninja file. 77 "--ninja", 78 // Generate Ninja files in the output directory. 79 "--ninja_dir=" + config.OutDir(), 80 // Filename suffix of the generated Ninja file. 81 "--ninja_suffix=" + config.KatiSuffix() + extraSuffix, 82 // Remove common parts at the beginning of a Ninja file, like build_dir, 83 // local_pool and _kati_always_build_. Allows Kati to be run multiple 84 // times, with generated Ninja files combined in a single invocation 85 // using 'include'. 86 "--no_ninja_prelude", 87 // Support declaring phony outputs in AOSP Ninja. 88 "--use_ninja_phony_output", 89 // Support declaring symlink outputs in AOSP Ninja. 90 "--use_ninja_symlink_outputs", 91 // Regenerate the Ninja file if environment inputs have changed. e.g. 92 // CLI flags, .mk file timestamps, env vars, $(wildcard ..) and some 93 // $(shell ..) results. 94 "--regen", 95 // Skip '-include' directives starting with the specified path. Used to 96 // ignore generated .mk files. 97 "--ignore_optional_include=" + filepath.Join(config.OutDir(), "%.P"), 98 // Detect the use of $(shell echo ...). 99 "--detect_android_echo", 100 // Colorful ANSI-based warning and error messages. 101 "--color_warnings", 102 // Generate all targets, not just the top level requested ones. 103 "--gen_all_targets", 104 // Use the built-in emulator of GNU find for better file finding 105 // performance. Used with $(shell find ...). 106 "--use_find_emulator", 107 // Fail when the find emulator encounters problems. 108 "--werror_find_emulator", 109 // Do not provide any built-in rules. 110 "--no_builtin_rules", 111 // Fail when suffix rules are used. 112 "--werror_suffix_rules", 113 // Fail when a real target depends on a phony target. 114 "--werror_real_to_phony", 115 // Makes real_to_phony checks assume that any top-level or leaf 116 // dependencies that does *not* have a '/' in it is a phony target. 117 "--top_level_phony", 118 // Fail when a phony target contains slashes. 119 "--werror_phony_looks_real", 120 // Fail when writing to a read-only directory. 121 "--werror_writable", 122 // Print Kati's internal statistics, such as the number of variables, 123 // implicit/explicit/suffix rules, and so on. 124 "--kati_stats", 125 }, args...) 126 127 // Generate a minimal Ninja file. 128 // 129 // Used for build_test and multiproduct_kati, which runs Kati several 130 // hundred times for different configurations to test file generation logic. 131 // These can result in generating Ninja files reaching ~1GB or more, 132 // resulting in ~hundreds of GBs of writes. 133 // 134 // Since we don't care about executing the Ninja files in these test cases, 135 // generating the Ninja file content wastes time, so skip writing any 136 // information out with --empty_ninja_file. 137 // 138 // From https://github.com/google/kati/commit/87b8da7af2c8bea28b1d8ab17679453d859f96e5 139 if config.EmptyNinjaFile() { 140 args = append(args, "--empty_ninja_file") 141 } 142 143 // Apply 'local_pool' to to all rules that don't specify a pool. 144 if config.UseRemoteBuild() { 145 args = append(args, "--default_pool=local_pool") 146 } 147 148 cmd := Command(ctx, config, "ckati", executable, args...) 149 150 // Set up the nsjail sandbox. 151 cmd.Sandbox = katiSandbox 152 153 // Set up stdout and stderr. 154 pipe, err := cmd.StdoutPipe() 155 if err != nil { 156 ctx.Fatalln("Error getting output pipe for ckati:", err) 157 } 158 cmd.Stderr = cmd.Stdout 159 160 // Apply the caller's function closure to mutate the environment variables. 161 envFunc(cmd.Environment) 162 163 // Pass on various build environment metadata to Kati. 164 if _, ok := cmd.Environment.Get("BUILD_USERNAME"); !ok { 165 username := "unknown" 166 if u, err := user.Current(); err == nil { 167 username = u.Username 168 } else { 169 ctx.Println("Failed to get current user:", err) 170 } 171 cmd.Environment.Set("BUILD_USERNAME", username) 172 } 173 174 if _, ok := cmd.Environment.Get("BUILD_HOSTNAME"); !ok { 175 hostname, err := os.Hostname() 176 if err != nil { 177 ctx.Println("Failed to read hostname:", err) 178 hostname = "unknown" 179 } 180 cmd.Environment.Set("BUILD_HOSTNAME", hostname) 181 } 182 183 cmd.StartOrFatal() 184 // Set up the ToolStatus command line reader for Kati for a consistent UI 185 // for the user. 186 status.KatiReader(ctx.Status.StartTool(), pipe) 187 cmd.WaitOrFatal() 188} 189 190func runKatiBuild(ctx Context, config Config) { 191 ctx.BeginTrace(metrics.RunKati, "kati build") 192 defer ctx.EndTrace() 193 194 args := []string{ 195 // Mark the output directory as writable. 196 "--writable", config.OutDir() + "/", 197 // Fail when encountering implicit rules. e.g. 198 // %.foo: %.bar 199 // cp $< $@ 200 "--werror_implicit_rules", 201 // Entry point for the Kati Ninja file generation. 202 "-f", "build/make/core/main.mk", 203 } 204 205 if !config.BuildBrokenDupRules() { 206 // Fail when redefining / duplicating a target. 207 args = append(args, "--werror_overriding_commands") 208 } 209 210 args = append(args, config.KatiArgs()...) 211 212 args = append(args, 213 // Location of the Make vars .mk file generated by Soong. 214 "SOONG_MAKEVARS_MK="+config.SoongMakeVarsMk(), 215 // Location of the Android.mk file generated by Soong. This 216 // file contains Soong modules represented as Kati modules, 217 // allowing Kati modules to depend on Soong modules. 218 "SOONG_ANDROID_MK="+config.SoongAndroidMk(), 219 // Directory containing outputs for the target device. 220 "TARGET_DEVICE_DIR="+config.TargetDeviceDir(), 221 // Directory containing .mk files for packaging purposes, such as 222 // the dist.mk file, containing dist-for-goals data. 223 "KATI_PACKAGE_MK_DIR="+config.KatiPackageMkDir()) 224 225 runKati(ctx, config, katiBuildSuffix, args, func(env *Environment) {}) 226 227 // compress and dist the main build ninja file. 228 distGzipFile(ctx, config, config.KatiBuildNinjaFile()) 229 230 // Cleanup steps. 231 cleanCopyHeaders(ctx, config) 232 cleanOldInstalledFiles(ctx, config) 233} 234 235// Clean out obsolete header files on the disk that were *not copied* during the 236// build with BUILD_COPY_HEADERS and LOCAL_COPY_HEADERS. 237// 238// These should be increasingly uncommon, as it's a deprecated feature and there 239// isn't an equivalent feature in Soong. 240func cleanCopyHeaders(ctx Context, config Config) { 241 ctx.BeginTrace("clean", "clean copy headers") 242 defer ctx.EndTrace() 243 244 // Read and parse the list of copied headers from a file in the product 245 // output directory. 246 data, err := ioutil.ReadFile(filepath.Join(config.ProductOut(), ".copied_headers_list")) 247 if err != nil { 248 if os.IsNotExist(err) { 249 return 250 } 251 ctx.Fatalf("Failed to read copied headers list: %v", err) 252 } 253 254 headers := strings.Fields(string(data)) 255 if len(headers) < 1 { 256 ctx.Fatal("Failed to parse copied headers list: %q", string(data)) 257 } 258 headerDir := headers[0] 259 headers = headers[1:] 260 261 // Walk the tree and remove any headers that are not in the list of copied 262 // headers in the current build. 263 filepath.Walk(headerDir, 264 func(path string, info os.FileInfo, err error) error { 265 if err != nil { 266 return nil 267 } 268 if info.IsDir() { 269 return nil 270 } 271 if !inList(path, headers) { 272 ctx.Printf("Removing obsolete header %q", path) 273 if err := os.Remove(path); err != nil { 274 ctx.Fatalf("Failed to remove obsolete header %q: %v", path, err) 275 } 276 } 277 return nil 278 }) 279} 280 281// Clean out any previously installed files from the disk that are not installed 282// in the current build. 283func cleanOldInstalledFiles(ctx Context, config Config) { 284 ctx.BeginTrace("clean", "clean old installed files") 285 defer ctx.EndTrace() 286 287 // We shouldn't be removing files from one side of the two-step asan builds 288 var suffix string 289 if v, ok := config.Environment().Get("SANITIZE_TARGET"); ok { 290 if sanitize := strings.Fields(v); inList("address", sanitize) { 291 suffix = "_asan" 292 } 293 } 294 295 cleanOldFiles(ctx, config.ProductOut(), ".installable_files"+suffix) 296 297 cleanOldFiles(ctx, config.HostOut(), ".installable_test_files") 298} 299 300// Generate the Ninja file containing the packaging command lines for the dist 301// dir. 302func runKatiPackage(ctx Context, config Config) { 303 ctx.BeginTrace(metrics.RunKati, "kati package") 304 defer ctx.EndTrace() 305 306 args := []string{ 307 // Mark the dist dir as writable. 308 "--writable", config.DistDir() + "/", 309 // Fail when encountering implicit rules. e.g. 310 "--werror_implicit_rules", 311 // Fail when redefining / duplicating a target. 312 "--werror_overriding_commands", 313 // Entry point. 314 "-f", "build/make/packaging/main.mk", 315 // Directory containing .mk files for packaging purposes, such as 316 // the dist.mk file, containing dist-for-goals data. 317 "KATI_PACKAGE_MK_DIR=" + config.KatiPackageMkDir(), 318 } 319 320 // Run Kati against a restricted set of environment variables. 321 runKati(ctx, config, katiPackageSuffix, args, func(env *Environment) { 322 env.Allow([]string{ 323 // Some generic basics 324 "LANG", 325 "LC_MESSAGES", 326 "PATH", 327 "PWD", 328 "TMPDIR", 329 330 // Tool configs 331 "ASAN_SYMBOLIZER_PATH", 332 "JAVA_HOME", 333 "PYTHONDONTWRITEBYTECODE", 334 335 // Build configuration 336 "ANDROID_BUILD_SHELL", 337 "DIST_DIR", 338 "OUT_DIR", 339 }...) 340 341 if config.Dist() { 342 env.Set("DIST", "true") 343 env.Set("DIST_DIR", config.DistDir()) 344 } 345 }) 346 347 // Compress and dist the packaging Ninja file. 348 distGzipFile(ctx, config, config.KatiPackageNinjaFile()) 349} 350 351// Run Kati on the cleanspec files to clean the build. 352func runKatiCleanSpec(ctx Context, config Config) { 353 ctx.BeginTrace(metrics.RunKati, "kati cleanspec") 354 defer ctx.EndTrace() 355 356 runKati(ctx, config, katiCleanspecSuffix, []string{ 357 // Fail when encountering implicit rules. e.g. 358 "--werror_implicit_rules", 359 // Fail when redefining / duplicating a target. 360 "--werror_overriding_commands", 361 // Entry point. 362 "-f", "build/make/core/cleanbuild.mk", 363 "SOONG_MAKEVARS_MK=" + config.SoongMakeVarsMk(), 364 "TARGET_DEVICE_DIR=" + config.TargetDeviceDir(), 365 }, func(env *Environment) {}) 366} 367