1// Copyright 2019 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 java 16 17import ( 18 "path/filepath" 19 "sort" 20 "strings" 21 22 "android/soong/android" 23 "android/soong/dexpreopt" 24 25 "github.com/google/blueprint/proptools" 26) 27 28// ================================================================================================= 29// WIP - see http://b/177892522 for details 30// 31// The build support for boot images is currently being migrated away from singleton to modules so 32// the documentation may not be strictly accurate. Rather than update the documentation at every 33// step which will create a lot of churn the changes that have been made will be listed here and the 34// documentation will be updated once it is closer to the final result. 35// 36// Changes: 37// 1) dex_bootjars is now a singleton module and not a plain singleton. 38// 2) Boot images are now represented by the boot_image module type. 39// 3) The art boot image is called "art-boot-image", the framework boot image is called 40// "framework-boot-image". 41// 4) They are defined in art/build/boot/Android.bp and frameworks/base/boot/Android.bp 42// respectively. 43// 5) Each boot_image retrieves the appropriate boot image configuration from the map returned by 44// genBootImageConfigs() using the image_name specified in the boot_image module. 45// ================================================================================================= 46 47// This comment describes: 48// 1. ART boot images in general (their types, structure, file layout, etc.) 49// 2. build system support for boot images 50// 51// 1. ART boot images 52// ------------------ 53// 54// A boot image in ART is a set of files that contain AOT-compiled native code and a heap snapshot 55// of AOT-initialized classes for the bootclasspath Java libraries. A boot image is compiled from a 56// set of DEX jars by the dex2oat compiler. A boot image is used for two purposes: 1) it is 57// installed on device and loaded at runtime, and 2) other Java libraries and apps are compiled 58// against it (compilation may take place either on host, known as "dexpreopt", or on device, known 59// as "dexopt"). 60// 61// A boot image is not a single file, but a collection of interrelated files. Each boot image has a 62// number of components that correspond to the Java libraries that constitute it. For each component 63// there are multiple files: 64// - *.oat or *.odex file with native code (architecture-specific, one per instruction set) 65// - *.art file with pre-initialized Java classes (architecture-specific, one per instruction set) 66// - *.vdex file with verification metadata for the DEX bytecode (architecture independent) 67// 68// *.vdex files for the boot images do not contain the DEX bytecode itself, because the 69// bootclasspath DEX files are stored on disk in uncompressed and aligned form. Consequently a boot 70// image is not self-contained and cannot be used without its DEX files. To simplify the management 71// of boot image files, ART uses a certain naming scheme and associates the following metadata with 72// each boot image: 73// - A stem, which is a symbolic name that is prepended to boot image file names. 74// - A location (on-device path to the boot image files). 75// - A list of boot image locations (on-device paths to dependency boot images). 76// - A set of DEX locations (on-device paths to the DEX files, one location for one DEX file used 77// to compile the boot image). 78// 79// There are two kinds of boot images: 80// - primary boot images 81// - boot image extensions 82// 83// 1.1. Primary boot images 84// ------------------------ 85// 86// A primary boot image is compiled for a core subset of bootclasspath Java libraries. It does not 87// depend on any other images, and other boot images may depend on it. 88// 89// For example, assuming that the stem is "boot", the location is /apex/com.android.art/javalib/, 90// the set of core bootclasspath libraries is A B C, and the boot image is compiled for ARM targets 91// (32 and 64 bits), it will have three components with the following files: 92// - /apex/com.android.art/javalib/{arm,arm64}/boot.{art,oat,vdex} 93// - /apex/com.android.art/javalib/{arm,arm64}/boot-B.{art,oat,vdex} 94// - /apex/com.android.art/javalib/{arm,arm64}/boot-C.{art,oat,vdex} 95// 96// The files of the first component are special: they do not have the component name appended after 97// the stem. This naming convention dates back to the times when the boot image was not split into 98// components, and there were just boot.oat and boot.art. The decision to split was motivated by 99// licensing reasons for one of the bootclasspath libraries. 100// 101// As of November 2020 the only primary boot image in Android is the image in the ART APEX 102// com.android.art. The primary ART boot image contains the Core libraries that are part of the ART 103// module. When the ART module gets updated, the primary boot image will be updated with it, and all 104// dependent images will get invalidated (the checksum of the primary image stored in dependent 105// images will not match), unless they are updated in sync with the ART module. 106// 107// 1.2. Boot image extensions 108// -------------------------- 109// 110// A boot image extension is compiled for a subset of bootclasspath Java libraries (in particular, 111// this subset does not include the Core bootclasspath libraries that go into the primary boot 112// image). A boot image extension depends on the primary boot image and optionally some other boot 113// image extensions. Other images may depend on it. In other words, boot image extensions can form 114// acyclic dependency graphs. 115// 116// The motivation for boot image extensions comes from the Mainline project. Consider a situation 117// when the list of bootclasspath libraries is A B C, and both A and B are parts of the Android 118// platform, but C is part of an updatable APEX com.android.C. When the APEX is updated, the Java 119// code for C might have changed compared to the code that was used to compile the boot image. 120// Consequently, the whole boot image is obsolete and invalidated (even though the code for A and B 121// that does not depend on C is up to date). To avoid this, the original monolithic boot image is 122// split in two parts: the primary boot image that contains A B, and the boot image extension that 123// contains C and depends on the primary boot image (extends it). 124// 125// For example, assuming that the stem is "boot", the location is /system/framework, the set of 126// bootclasspath libraries is D E (where D is part of the platform and is located in 127// /system/framework, and E is part of a non-updatable APEX com.android.E and is located in 128// /apex/com.android.E/javalib), and the boot image is compiled for ARM targets (32 and 64 bits), 129// it will have two components with the following files: 130// - /system/framework/{arm,arm64}/boot-D.{art,oat,vdex} 131// - /system/framework/{arm,arm64}/boot-E.{art,oat,vdex} 132// 133// As of November 2020 the only boot image extension in Android is the Framework boot image 134// extension. It extends the primary ART boot image and contains Framework libraries and other 135// bootclasspath libraries from the platform and non-updatable APEXes that are not included in the 136// ART image. The Framework boot image extension is updated together with the platform. In the 137// future other boot image extensions may be added for some updatable modules. 138// 139// 140// 2. Build system support for boot images 141// --------------------------------------- 142// 143// The primary ART boot image needs to be compiled with one dex2oat invocation that depends on DEX 144// jars for the core libraries. Framework boot image extension needs to be compiled with one dex2oat 145// invocation that depends on the primary ART boot image and all bootclasspath DEX jars except the 146// core libraries as they are already part of the primary ART boot image. 147// 148// 2.1. Libraries that go in the boot images 149// ----------------------------------------- 150// 151// The contents of each boot image are determined by the PRODUCT variables. The primary ART APEX 152// boot image contains libraries listed in the ART_APEX_JARS variable in the AOSP makefiles. The 153// Framework boot image extension contains libraries specified in the PRODUCT_BOOT_JARS and 154// PRODUCT_BOOT_JARS_EXTRA variables. The AOSP makefiles specify some common Framework libraries, 155// but more product-specific libraries can be added in the product makefiles. 156// 157// Each component of the PRODUCT_BOOT_JARS and PRODUCT_BOOT_JARS_EXTRA variables is a 158// colon-separated pair <apex>:<library>, where <apex> is the variant name of a non-updatable APEX, 159// "platform" if the library is a part of the platform in the system partition, or "system_ext" if 160// it's in the system_ext partition. 161// 162// In these variables APEXes are identified by their "variant names", i.e. the names they get 163// mounted as in /apex on device. In Soong modules that is the name set in the "apex_name" 164// properties, which default to the "name" values. For example, many APEXes have both 165// com.android.xxx and com.google.android.xxx modules in Soong, but take the same place 166// /apex/com.android.xxx at runtime. In these cases the variant name is always com.android.xxx, 167// regardless which APEX goes into the product. See also android.ApexInfo.ApexVariationName and 168// apex.apexBundleProperties.Apex_name. 169// 170// A related variable PRODUCT_APEX_BOOT_JARS contains bootclasspath libraries that are in APEXes. 171// They are not included in the boot image. The only exception here are ART jars and core-icu4j.jar 172// that have been historically part of the boot image and are now in apexes; they are in boot images 173// and core-icu4j.jar is generally treated as being part of PRODUCT_BOOT_JARS. 174// 175// One exception to the above rules are "coverage" builds (a special build flavor which requires 176// setting environment variable EMMA_INSTRUMENT_FRAMEWORK=true). In coverage builds the Java code in 177// boot image libraries is instrumented, which means that the instrumentation library (jacocoagent) 178// needs to be added to the list of bootclasspath DEX jars. 179// 180// In general, there is a requirement that the source code for a boot image library must be 181// available at build time (e.g. it cannot be a stub that has a separate implementation library). 182// 183// 2.2. Static configs 184// ------------------- 185// 186// Because boot images are used to dexpreopt other Java modules, the paths to boot image files must 187// be known by the time dexpreopt build rules for the dependent modules are generated. Boot image 188// configs are constructed very early during the build, before build rule generation. The configs 189// provide predefined paths to boot image files (these paths depend only on static build 190// configuration, such as PRODUCT variables, and use hard-coded directory names). 191// 192// 2.3. Singleton 193// -------------- 194// 195// Build rules for the boot images are generated with a Soong singleton. Because a singleton has no 196// dependencies on other modules, it has to find the modules for the DEX jars using VisitAllModules. 197// Soong loops through all modules and compares each module against a list of bootclasspath library 198// names. Then it generates build rules that copy DEX jars from their intermediate module-specific 199// locations to the hard-coded locations predefined in the boot image configs. 200// 201// It would be possible to use a module with proper dependencies instead, but that would require 202// changes in the way Soong generates variables for Make: a singleton can use one MakeVars() method 203// that writes variables to out/soong/make_vars-*.mk, which is included early by the main makefile, 204// but module(s) would have to use out/soong/Android-*.mk which has a group of LOCAL_* variables 205// for each module, and is included later. 206// 207// 2.4. Install rules 208// ------------------ 209// 210// The primary boot image and the Framework extension are installed in different ways. The primary 211// boot image is part of the ART APEX: it is copied into the APEX intermediate files, packaged 212// together with other APEX contents, extracted and mounted on device. The Framework boot image 213// extension is installed by the rules defined in makefiles (make/core/dex_preopt_libart.mk). Soong 214// writes out a few DEXPREOPT_IMAGE_* variables for Make; these variables contain boot image names, 215// paths and so on. 216// 217 218var artApexNames = []string{ 219 "com.android.art", 220 "com.android.art.debug", 221 "com.android.art.testing", 222 "com.google.android.art", 223 "com.google.android.art.debug", 224 "com.google.android.art.testing", 225} 226 227func init() { 228 RegisterDexpreoptBootJarsComponents(android.InitRegistrationContext) 229} 230 231// Target-independent description of a boot image. 232// 233// WARNING: All fields in this struct should be initialized in the genBootImageConfigs function. 234// Failure to do so can lead to data races if there is no synchronization enforced ordering between 235// the writer and the reader. Fields which break this rule are marked as deprecated and should be 236// removed and replaced with something else, e.g. providers. 237type bootImageConfig struct { 238 // If this image is an extension, the image that it extends. 239 extends *bootImageConfig 240 241 // Image name (used in directory names and ninja rule names). 242 name string 243 244 // Basename of the image: the resulting filenames are <stem>[-<jar>].{art,oat,vdex}. 245 stem string 246 247 // Output directory for the image files. 248 dir android.OutputPath 249 250 // Output directory for the image files with debug symbols. 251 symbolsDir android.OutputPath 252 253 // The relative location where the image files are installed. On host, the location is relative to 254 // $ANDROID_PRODUCT_OUT. 255 // 256 // Only the configs that are built by platform_bootclasspath are installable on device. On device, 257 // the location is relative to "/". 258 installDir string 259 260 // Install path of the boot image profile if it needs to be installed in the APEX, or empty if not 261 // needed. 262 profileInstallPathInApex string 263 264 // A list of (location, jar) pairs for the Java modules in this image. 265 modules android.ConfiguredJarList 266 267 // File paths to jars. 268 dexPaths android.WritablePaths // for this image 269 dexPathsDeps android.WritablePaths // for the dependency images and in this image 270 271 // Map from module name (without prebuilt_ prefix) to the predefined build path. 272 dexPathsByModule map[string]android.WritablePath 273 274 // File path to a zip archive with all image files (or nil, if not needed). 275 zip android.WritablePath 276 277 // Rules which should be used in make to install the outputs. 278 // 279 // Deprecated: Not initialized correctly, see struct comment. 280 profileInstalls android.RuleBuilderInstalls 281 282 // Path to the license metadata file for the module that built the profile. 283 // 284 // Deprecated: Not initialized correctly, see struct comment. 285 profileLicenseMetadataFile android.OptionalPath 286 287 // Target-dependent fields. 288 variants []*bootImageVariant 289 290 // Path of the preloaded classes file. 291 preloadedClassesFile string 292 293 // The "--compiler-filter" argument. 294 compilerFilter string 295 296 // The "--single-image" argument. 297 singleImage bool 298 299 // Profiles imported from other boot image configs. Each element must represent a 300 // `bootclasspath_fragment` of an APEX (i.e., the `name` field of each element must refer to the 301 // `image_name` property of a `bootclasspath_fragment`). 302 profileImports []*bootImageConfig 303} 304 305// Target-dependent description of a boot image. 306// 307// WARNING: The warning comment on bootImageConfig applies here too. 308type bootImageVariant struct { 309 *bootImageConfig 310 311 // Target for which the image is generated. 312 target android.Target 313 314 // The "locations" of jars. 315 dexLocations []string // for this image 316 dexLocationsDeps []string // for the dependency images and in this image 317 318 // Paths to image files. 319 imagePathOnHost android.OutputPath // first image file path on host 320 imagePathOnDevice string // first image file path on device 321 322 // All the files that constitute this image variant, i.e. .art, .oat and .vdex files. 323 imagesDeps android.OutputPaths 324 325 // The path to the base image variant's imagePathOnHost field, where base image variant 326 // means the image variant that this extends. 327 // 328 // This is only set for a variant of an image that extends another image. 329 baseImages android.OutputPaths 330 331 // The paths to the base image variant's imagesDeps field, where base image variant 332 // means the image variant that this extends. 333 // 334 // This is only set for a variant of an image that extends another image. 335 baseImagesDeps android.Paths 336 337 // Rules which should be used in make to install the outputs on host. 338 // 339 // Deprecated: Not initialized correctly, see struct comment. 340 installs android.RuleBuilderInstalls 341 342 // Rules which should be used in make to install the vdex outputs on host. 343 // 344 // Deprecated: Not initialized correctly, see struct comment. 345 vdexInstalls android.RuleBuilderInstalls 346 347 // Rules which should be used in make to install the unstripped outputs on host. 348 // 349 // Deprecated: Not initialized correctly, see struct comment. 350 unstrippedInstalls android.RuleBuilderInstalls 351 352 // Path to the license metadata file for the module that built the image. 353 // 354 // Deprecated: Not initialized correctly, see struct comment. 355 licenseMetadataFile android.OptionalPath 356} 357 358// Get target-specific boot image variant for the given boot image config and target. 359func (image bootImageConfig) getVariant(target android.Target) *bootImageVariant { 360 for _, variant := range image.variants { 361 if variant.target.Os == target.Os && variant.target.Arch.ArchType == target.Arch.ArchType { 362 return variant 363 } 364 } 365 return nil 366} 367 368// Return any (the first) variant which is for the device (as opposed to for the host). 369func (image bootImageConfig) getAnyAndroidVariant() *bootImageVariant { 370 for _, variant := range image.variants { 371 if variant.target.Os == android.Android { 372 return variant 373 } 374 } 375 return nil 376} 377 378// Return the name of a boot image module given a boot image config and a component (module) index. 379// A module name is a combination of the Java library name, and the boot image stem (that is stored 380// in the config). 381func (image bootImageConfig) moduleName(ctx android.PathContext, idx int) string { 382 // The first module of the primary boot image is special: its module name has only the stem, but 383 // not the library name. All other module names are of the form <stem>-<library name> 384 m := image.modules.Jar(idx) 385 name := image.stem 386 if idx != 0 || image.extends != nil { 387 name += "-" + android.ModuleStem(m) 388 } 389 return name 390} 391 392// Return the name of the first boot image module, or stem if the list of modules is empty. 393func (image bootImageConfig) firstModuleNameOrStem(ctx android.PathContext) string { 394 if image.modules.Len() > 0 { 395 return image.moduleName(ctx, 0) 396 } else { 397 return image.stem 398 } 399} 400 401// Return filenames for the given boot image component, given the output directory and a list of 402// extensions. 403func (image bootImageConfig) moduleFiles(ctx android.PathContext, dir android.OutputPath, exts ...string) android.OutputPaths { 404 ret := make(android.OutputPaths, 0, image.modules.Len()*len(exts)) 405 for i := 0; i < image.modules.Len(); i++ { 406 name := image.moduleName(ctx, i) 407 for _, ext := range exts { 408 ret = append(ret, dir.Join(ctx, name+ext)) 409 } 410 if image.singleImage { 411 break 412 } 413 } 414 return ret 415} 416 417// apexVariants returns a list of all *bootImageVariant that could be included in an apex. 418func (image *bootImageConfig) apexVariants() []*bootImageVariant { 419 variants := []*bootImageVariant{} 420 for _, variant := range image.variants { 421 // We also generate boot images for host (for testing), but we don't need those in the apex. 422 // TODO(b/177892522) - consider changing this to check Os.OsClass = android.Device 423 if variant.target.Os == android.Android { 424 variants = append(variants, variant) 425 } 426 } 427 return variants 428} 429 430// Return boot image locations (as a list of symbolic paths). 431// 432// The image "location" is a symbolic path that, with multiarchitecture support, doesn't really 433// exist on the device. Typically it is /apex/com.android.art/javalib/boot.art and should be the 434// same for all supported architectures on the device. The concrete architecture specific files 435// actually end up in architecture-specific sub-directory such as arm, arm64, x86, or x86_64. 436// 437// For example a physical file /apex/com.android.art/javalib/x86/boot.art has "image location" 438// /apex/com.android.art/javalib/boot.art (which is not an actual file). 439// 440// For a primary boot image the list of locations has a single element. 441// 442// For a boot image extension the list of locations contains a location for all dependency images 443// (including the primary image) and the location of the extension itself. For example, for the 444// Framework boot image extension that depends on the primary ART boot image the list contains two 445// elements. 446// 447// The location is passed as an argument to the ART tools like dex2oat instead of the real path. 448// ART tools will then reconstruct the architecture-specific real path. 449func (image *bootImageVariant) imageLocations() (imageLocationsOnHost []string, imageLocationsOnDevice []string) { 450 if image.extends != nil { 451 imageLocationsOnHost, imageLocationsOnDevice = image.extends.getVariant(image.target).imageLocations() 452 } 453 return append(imageLocationsOnHost, dexpreopt.PathToLocation(image.imagePathOnHost, image.target.Arch.ArchType)), 454 append(imageLocationsOnDevice, dexpreopt.PathStringToLocation(image.imagePathOnDevice, image.target.Arch.ArchType)) 455} 456 457func (image *bootImageConfig) isProfileGuided() bool { 458 return image.compilerFilter == "speed-profile" 459} 460 461func dexpreoptBootJarsFactory() android.SingletonModule { 462 m := &dexpreoptBootJars{} 463 android.InitAndroidModule(m) 464 return m 465} 466 467func RegisterDexpreoptBootJarsComponents(ctx android.RegistrationContext) { 468 ctx.RegisterSingletonModuleType("dex_bootjars", dexpreoptBootJarsFactory) 469} 470 471func SkipDexpreoptBootJars(ctx android.PathContext) bool { 472 return dexpreopt.GetGlobalConfig(ctx).DisablePreoptBootImages 473} 474 475// Singleton module for generating boot image build rules. 476type dexpreoptBootJars struct { 477 android.SingletonModuleBase 478 479 // Default boot image config (currently always the Framework boot image extension). It should be 480 // noted that JIT-Zygote builds use ART APEX image instead of the Framework boot image extension, 481 // but the switch is handled not here, but in the makefiles (triggered with 482 // DEXPREOPT_USE_ART_IMAGE=true). 483 defaultBootImage *bootImageConfig 484 485 // Other boot image configs (currently the list contains only the primary ART APEX image. It 486 // used to contain an experimental JIT-Zygote image (now replaced with the ART APEX image). In 487 // the future other boot image extensions may be added. 488 otherImages []*bootImageConfig 489 490 // Build path to a config file that Soong writes for Make (to be used in makefiles that install 491 // the default boot image). 492 dexpreoptConfigForMake android.WritablePath 493} 494 495// Provide paths to boot images for use by modules that depend upon them. 496// 497// The build rules are created in GenerateSingletonBuildActions(). 498func (d *dexpreoptBootJars) GenerateAndroidBuildActions(ctx android.ModuleContext) { 499 // Placeholder for now. 500} 501 502// Generate build rules for boot images. 503func (d *dexpreoptBootJars) GenerateSingletonBuildActions(ctx android.SingletonContext) { 504 if dexpreopt.GetCachedGlobalSoongConfig(ctx) == nil { 505 // No module has enabled dexpreopting, so we assume there will be no boot image to make. 506 return 507 } 508 archType := ctx.Config().Targets[android.Android][0].Arch.ArchType 509 d.dexpreoptConfigForMake = android.PathForOutput(ctx, toDexpreoptDirName(archType), "dexpreopt.config") 510 writeGlobalConfigForMake(ctx, d.dexpreoptConfigForMake) 511 512 global := dexpreopt.GetGlobalConfig(ctx) 513 if !shouldBuildBootImages(ctx.Config(), global) { 514 return 515 } 516 517 defaultImageConfig := defaultBootImageConfig(ctx) 518 d.defaultBootImage = defaultImageConfig 519 imageConfigs := genBootImageConfigs(ctx) 520 d.otherImages = make([]*bootImageConfig, 0, len(imageConfigs)-1) 521 for _, config := range imageConfigs { 522 if config != defaultImageConfig { 523 d.otherImages = append(d.otherImages, config) 524 } 525 } 526} 527 528// shouldBuildBootImages determines whether boot images should be built. 529func shouldBuildBootImages(config android.Config, global *dexpreopt.GlobalConfig) bool { 530 // Skip recompiling the boot image for the second sanitization phase. We'll get separate paths 531 // and invalidate first-stage artifacts which are crucial to SANITIZE_LITE builds. 532 // Note: this is technically incorrect. Compiled code contains stack checks which may depend 533 // on ASAN settings. 534 if len(config.SanitizeDevice()) == 1 && config.SanitizeDevice()[0] == "address" && global.SanitizeLite { 535 return false 536 } 537 return true 538} 539 540// copyBootJarsToPredefinedLocations generates commands that will copy boot jars to predefined 541// paths in the global config. 542func copyBootJarsToPredefinedLocations(ctx android.ModuleContext, srcBootDexJarsByModule bootDexJarByModule, dstBootJarsByModule map[string]android.WritablePath) { 543 // Create the super set of module names. 544 names := []string{} 545 names = append(names, android.SortedKeys(srcBootDexJarsByModule)...) 546 names = append(names, android.SortedKeys(dstBootJarsByModule)...) 547 names = android.SortedUniqueStrings(names) 548 for _, name := range names { 549 src := srcBootDexJarsByModule[name] 550 dst := dstBootJarsByModule[name] 551 552 if src == nil { 553 // A dex boot jar should be provided by the source java module. It needs to be installable or 554 // have compile_dex=true - cf. assignments to java.Module.dexJarFile. 555 // 556 // However, the source java module may be either replaced or overridden (using prefer:true) by 557 // a prebuilt java module with the same name. In that case the dex boot jar needs to be 558 // provided by the corresponding prebuilt APEX module. That APEX is the one that refers 559 // through a exported_(boot|systemserver)classpath_fragments property to a 560 // prebuilt_(boot|systemserver)classpath_fragment module, which in turn lists the prebuilt 561 // java module in the contents property. If that chain is broken then this dependency will 562 // fail. 563 if !ctx.Config().AllowMissingDependencies() { 564 ctx.ModuleErrorf("module %s does not provide a dex boot jar (see comment next to this message in Soong for details)", name) 565 } else { 566 ctx.AddMissingDependencies([]string{name}) 567 } 568 } else if dst == nil { 569 ctx.ModuleErrorf("module %s is not part of the boot configuration", name) 570 } else { 571 ctx.Build(pctx, android.BuildParams{ 572 Rule: android.Cp, 573 Input: src, 574 Output: dst, 575 }) 576 } 577 } 578} 579 580// buildBootImageVariantsForAndroidOs generates rules to build the boot image variants for the 581// android.Android OsType and returns a map from the architectures to the paths of the generated 582// boot image files. 583// 584// The paths are returned because they are needed elsewhere in Soong, e.g. for populating an APEX. 585func buildBootImageVariantsForAndroidOs(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) bootImageOutputs { 586 return buildBootImageForOsType(ctx, image, profile, android.Android) 587} 588 589// buildBootImageVariantsForBuildOs generates rules to build the boot image variants for the 590// config.BuildOS OsType, i.e. the type of OS on which the build is being running. 591// 592// The files need to be generated into their predefined location because they are used from there 593// both within Soong and outside, e.g. for ART based host side testing and also for use by some 594// cloud based tools. However, they are not needed by callers of this function and so the paths do 595// not need to be returned from this func, unlike the buildBootImageVariantsForAndroidOs func. 596func buildBootImageVariantsForBuildOs(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath) { 597 buildBootImageForOsType(ctx, image, profile, ctx.Config().BuildOS) 598} 599 600// bootImageFilesByArch is a map from android.ArchType to the paths to the boot image files. 601// 602// The paths include the .art, .oat and .vdex files, one for each of the modules from which the boot 603// image is created. 604type bootImageFilesByArch map[android.ArchType]android.Paths 605 606// bootImageOutputs encapsulates information about boot images that were created/obtained by 607// commonBootclasspathFragment.produceBootImageFiles. 608type bootImageOutputs struct { 609 // Map from arch to the paths to the boot image files created/obtained for that arch. 610 byArch bootImageFilesByArch 611 612 variants []bootImageVariantOutputs 613 614 // The path to the profile file created/obtained for the boot image. 615 profile android.WritablePath 616} 617 618// buildBootImageForOsType takes a bootImageConfig, a profile file and an android.OsType 619// boot image files are required for and it creates rules to build the boot image 620// files for all the required architectures for them. 621// 622// It returns a map from android.ArchType to the predefined paths of the boot image files. 623func buildBootImageForOsType(ctx android.ModuleContext, image *bootImageConfig, profile android.WritablePath, requiredOsType android.OsType) bootImageOutputs { 624 filesByArch := bootImageFilesByArch{} 625 imageOutputs := bootImageOutputs{ 626 byArch: filesByArch, 627 profile: profile, 628 } 629 for _, variant := range image.variants { 630 if variant.target.Os == requiredOsType { 631 variantOutputs := buildBootImageVariant(ctx, variant, profile) 632 imageOutputs.variants = append(imageOutputs.variants, variantOutputs) 633 filesByArch[variant.target.Arch.ArchType] = variant.imagesDeps.Paths() 634 } 635 } 636 637 return imageOutputs 638} 639 640// buildBootImageZipInPredefinedLocation generates a zip file containing all the boot image files. 641// 642// The supplied filesByArch is nil when the boot image files have not been generated. Otherwise, it 643// is a map from android.ArchType to the predefined locations. 644func buildBootImageZipInPredefinedLocation(ctx android.ModuleContext, image *bootImageConfig, filesByArch bootImageFilesByArch) { 645 if filesByArch == nil { 646 return 647 } 648 649 // Compute the list of files from all the architectures. 650 zipFiles := android.Paths{} 651 for _, archType := range android.ArchTypeList() { 652 zipFiles = append(zipFiles, filesByArch[archType]...) 653 } 654 655 rule := android.NewRuleBuilder(pctx, ctx) 656 rule.Command(). 657 BuiltTool("soong_zip"). 658 FlagWithOutput("-o ", image.zip). 659 FlagWithArg("-C ", image.dir.Join(ctx, android.Android.String()).String()). 660 FlagWithInputList("-f ", zipFiles, " -f ") 661 662 rule.Build("zip_"+image.name, "zip "+image.name+" image") 663} 664 665type bootImageVariantOutputs struct { 666 config *bootImageVariant 667} 668 669// Generate boot image build rules for a specific target. 670func buildBootImageVariant(ctx android.ModuleContext, image *bootImageVariant, profile android.Path) bootImageVariantOutputs { 671 672 globalSoong := dexpreopt.GetGlobalSoongConfig(ctx) 673 global := dexpreopt.GetGlobalConfig(ctx) 674 675 arch := image.target.Arch.ArchType 676 os := image.target.Os.String() // We need to distinguish host-x86 and device-x86. 677 symbolsDir := image.symbolsDir.Join(ctx, os, image.installDir, arch.String()) 678 symbolsFile := symbolsDir.Join(ctx, image.stem+".oat") 679 outputDir := image.dir.Join(ctx, os, image.installDir, arch.String()) 680 outputPath := outputDir.Join(ctx, image.stem+".oat") 681 oatLocation := dexpreopt.PathToLocation(outputPath, arch) 682 imagePath := outputPath.ReplaceExtension(ctx, "art") 683 684 rule := android.NewRuleBuilder(pctx, ctx) 685 686 rule.Command().Text("mkdir").Flag("-p").Flag(symbolsDir.String()) 687 rule.Command().Text("rm").Flag("-f"). 688 Flag(symbolsDir.Join(ctx, "*.art").String()). 689 Flag(symbolsDir.Join(ctx, "*.oat").String()). 690 Flag(symbolsDir.Join(ctx, "*.invocation").String()) 691 rule.Command().Text("rm").Flag("-f"). 692 Flag(outputDir.Join(ctx, "*.art").String()). 693 Flag(outputDir.Join(ctx, "*.oat").String()). 694 Flag(outputDir.Join(ctx, "*.invocation").String()) 695 696 cmd := rule.Command() 697 698 extraFlags := ctx.Config().Getenv("ART_BOOT_IMAGE_EXTRA_ARGS") 699 if extraFlags == "" { 700 // Use ANDROID_LOG_TAGS to suppress most logging by default... 701 cmd.Text(`ANDROID_LOG_TAGS="*:e"`) 702 } else { 703 // ...unless the boot image is generated specifically for testing, then allow all logging. 704 cmd.Text(`ANDROID_LOG_TAGS="*:v"`) 705 } 706 707 invocationPath := outputPath.ReplaceExtension(ctx, "invocation") 708 709 cmd.Tool(globalSoong.Dex2oat). 710 Flag("--avoid-storing-invocation"). 711 FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath). 712 Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatImageXms). 713 Flag("--runtime-arg").FlagWithArg("-Xmx", global.Dex2oatImageXmx) 714 715 if profile != nil { 716 cmd.FlagWithInput("--profile-file=", profile) 717 } 718 719 fragments := make(map[string]commonBootclasspathFragment) 720 ctx.VisitDirectDepsWithTag(bootclasspathFragmentDepTag, func(child android.Module) { 721 fragment := child.(commonBootclasspathFragment) 722 if fragment.getImageName() != nil && android.IsModulePreferred(child) { 723 fragments[*fragment.getImageName()] = fragment 724 } 725 }) 726 727 for _, profileImport := range image.profileImports { 728 fragment := fragments[profileImport.name] 729 if fragment == nil { 730 ctx.ModuleErrorf("Boot image config '%[1]s' imports profile from '%[2]s', but a "+ 731 "bootclasspath_fragment with image name '%[2]s' doesn't exist or is not added as a "+ 732 "dependency of '%[1]s'", 733 image.name, 734 profileImport.name) 735 return bootImageVariantOutputs{} 736 } 737 if fragment.getProfilePath() == nil { 738 ctx.ModuleErrorf("Boot image config '%[1]s' imports profile from '%[2]s', but '%[2]s' "+ 739 "doesn't provide a profile", 740 image.name, 741 profileImport.name) 742 return bootImageVariantOutputs{} 743 } 744 cmd.FlagWithInput("--profile-file=", fragment.getProfilePath()) 745 } 746 747 dirtyImageFile := "frameworks/base/config/dirty-image-objects" 748 dirtyImagePath := android.ExistentPathForSource(ctx, dirtyImageFile) 749 if dirtyImagePath.Valid() { 750 cmd.FlagWithInput("--dirty-image-objects=", dirtyImagePath.Path()) 751 } 752 753 if image.extends != nil { 754 // It is a boot image extension, so it needs the boot images that it depends on. 755 baseImageLocations := make([]string, 0, len(image.baseImages)) 756 for _, image := range image.baseImages { 757 baseImageLocations = append(baseImageLocations, dexpreopt.PathToLocation(image, arch)) 758 } 759 cmd. 760 Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", image.dexPathsDeps.Paths(), ":"). 761 Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", image.dexLocationsDeps, ":"). 762 // Add the path to the first file in the boot image with the arch specific directory removed, 763 // dex2oat will reconstruct the path to the actual file when it needs it. As the actual path 764 // to the file cannot be passed to the command make sure to add the actual path as an Implicit 765 // dependency to ensure that it is built before the command runs. 766 FlagWithList("--boot-image=", baseImageLocations, ":").Implicits(image.baseImages.Paths()). 767 // Similarly, the dex2oat tool will automatically find the paths to other files in the base 768 // boot image so make sure to add them as implicit dependencies to ensure that they are built 769 // before this command is run. 770 Implicits(image.baseImagesDeps) 771 } else { 772 // It is a primary image, so it needs a base address. 773 cmd.FlagWithArg("--base=", ctx.Config().LibartImgDeviceBaseAddress()) 774 } 775 776 if len(image.preloadedClassesFile) > 0 { 777 // We always expect a preloaded classes file to be available. However, if we cannot find it, it's 778 // OK to not pass the flag to dex2oat. 779 preloadedClassesPath := android.ExistentPathForSource(ctx, image.preloadedClassesFile) 780 if preloadedClassesPath.Valid() { 781 cmd.FlagWithInput("--preloaded-classes=", preloadedClassesPath.Path()) 782 } 783 } 784 785 cmd. 786 FlagForEachInput("--dex-file=", image.dexPaths.Paths()). 787 FlagForEachArg("--dex-location=", image.dexLocations). 788 Flag("--generate-debug-info"). 789 Flag("--generate-build-id"). 790 Flag("--image-format=lz4hc"). 791 FlagWithArg("--oat-symbols=", symbolsFile.String()). 792 Flag("--strip"). 793 FlagWithArg("--oat-file=", outputPath.String()). 794 FlagWithArg("--oat-location=", oatLocation). 795 FlagWithArg("--image=", imagePath.String()). 796 FlagWithArg("--instruction-set=", arch.String()). 797 FlagWithArg("--android-root=", global.EmptyDirectory). 798 FlagWithArg("--no-inline-from=", "core-oj.jar"). 799 Flag("--force-determinism"). 800 Flag("--abort-on-hard-verifier-error") 801 802 // If the image is profile-guided but the profile is disabled, we omit "--compiler-filter" to 803 // leave the decision to dex2oat to pick the compiler filter. 804 if !(image.isProfileGuided() && global.DisableGenerateProfile) { 805 cmd.FlagWithArg("--compiler-filter=", image.compilerFilter) 806 } 807 808 if image.singleImage { 809 cmd.Flag("--single-image") 810 } 811 812 // Use the default variant/features for host builds. 813 // The map below contains only device CPU info (which might be x86 on some devices). 814 if image.target.Os == android.Android { 815 cmd.FlagWithArg("--instruction-set-variant=", global.CpuVariant[arch]) 816 cmd.FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch]) 817 } 818 819 if global.EnableUffdGc { 820 cmd.Flag("--runtime-arg").Flag("-Xgc:CMC") 821 } 822 823 if global.BootFlags != "" { 824 cmd.Flag(global.BootFlags) 825 } 826 827 if extraFlags != "" { 828 cmd.Flag(extraFlags) 829 } 830 831 cmd.Textf(`|| ( echo %s ; false )`, proptools.ShellEscape(failureMessage)) 832 833 installDir := filepath.Dir(image.imagePathOnDevice) 834 835 var vdexInstalls android.RuleBuilderInstalls 836 var unstrippedInstalls android.RuleBuilderInstalls 837 838 for _, artOrOat := range image.moduleFiles(ctx, outputDir, ".art", ".oat") { 839 cmd.ImplicitOutput(artOrOat) 840 841 // Install the .oat and .art files 842 rule.Install(artOrOat, filepath.Join(installDir, artOrOat.Base())) 843 } 844 845 for _, vdex := range image.moduleFiles(ctx, outputDir, ".vdex") { 846 cmd.ImplicitOutput(vdex) 847 848 // Note that the vdex files are identical between architectures. 849 // Make rules will create symlinks to share them between architectures. 850 vdexInstalls = append(vdexInstalls, 851 android.RuleBuilderInstall{vdex, filepath.Join(installDir, vdex.Base())}) 852 } 853 854 for _, unstrippedOat := range image.moduleFiles(ctx, symbolsDir, ".oat") { 855 cmd.ImplicitOutput(unstrippedOat) 856 857 // Install the unstripped oat files. The Make rules will put these in $(TARGET_OUT_UNSTRIPPED) 858 unstrippedInstalls = append(unstrippedInstalls, 859 android.RuleBuilderInstall{unstrippedOat, filepath.Join(installDir, unstrippedOat.Base())}) 860 } 861 862 rule.Build(image.name+"JarsDexpreopt_"+image.target.String(), "dexpreopt "+image.name+" jars "+arch.String()) 863 864 // save output and installed files for makevars 865 // TODO - these are always the same and so should be initialized in genBootImageConfigs 866 image.installs = rule.Installs() 867 image.vdexInstalls = vdexInstalls 868 image.unstrippedInstalls = unstrippedInstalls 869 870 // Only set the licenseMetadataFile from the active module. 871 if isActiveModule(ctx.Module()) { 872 image.licenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile()) 873 } 874 875 return bootImageVariantOutputs{ 876 image, 877 } 878} 879 880const failureMessage = `ERROR: Dex2oat failed to compile a boot image. 881It is likely that the boot classpath is inconsistent. 882Rebuild with ART_BOOT_IMAGE_EXTRA_ARGS="--runtime-arg -verbose:verifier" to see verification errors.` 883 884func bootImageProfileRule(ctx android.ModuleContext, image *bootImageConfig) android.WritablePath { 885 if !image.isProfileGuided() { 886 return nil 887 } 888 889 globalSoong := dexpreopt.GetGlobalSoongConfig(ctx) 890 global := dexpreopt.GetGlobalConfig(ctx) 891 892 if global.DisableGenerateProfile { 893 return nil 894 } 895 896 defaultProfile := "frameworks/base/config/boot-image-profile.txt" 897 extraProfile := "frameworks/base/config/boot-image-profile-extra.txt" 898 899 rule := android.NewRuleBuilder(pctx, ctx) 900 901 var profiles android.Paths 902 if len(global.BootImageProfiles) > 0 { 903 profiles = append(profiles, global.BootImageProfiles...) 904 } else if path := android.ExistentPathForSource(ctx, defaultProfile); path.Valid() { 905 profiles = append(profiles, path.Path()) 906 } else { 907 // No profile (not even a default one, which is the case on some branches 908 // like master-art-host that don't have frameworks/base). 909 // Return nil and continue without profile. 910 return nil 911 } 912 if path := android.ExistentPathForSource(ctx, extraProfile); path.Valid() { 913 profiles = append(profiles, path.Path()) 914 } 915 bootImageProfile := image.dir.Join(ctx, "boot-image-profile.txt") 916 rule.Command().Text("cat").Inputs(profiles).Text(">").Output(bootImageProfile) 917 918 profile := image.dir.Join(ctx, "boot.prof") 919 920 rule.Command(). 921 Text(`ANDROID_LOG_TAGS="*:e"`). 922 Tool(globalSoong.Profman). 923 Flag("--output-profile-type=boot"). 924 FlagWithInput("--create-profile-from=", bootImageProfile). 925 FlagForEachInput("--apk=", image.dexPathsDeps.Paths()). 926 FlagForEachArg("--dex-location=", image.getAnyAndroidVariant().dexLocationsDeps). 927 FlagWithOutput("--reference-profile-file=", profile) 928 929 if image == defaultBootImageConfig(ctx) { 930 rule.Install(profile, "/system/etc/boot-image.prof") 931 image.profileInstalls = append(image.profileInstalls, rule.Installs()...) 932 image.profileLicenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile()) 933 } 934 935 rule.Build("bootJarsProfile", "profile boot jars") 936 937 return profile 938} 939 940// bootFrameworkProfileRule generates the rule to create the boot framework profile and 941// returns a path to the generated file. 942func bootFrameworkProfileRule(ctx android.ModuleContext, image *bootImageConfig) android.WritablePath { 943 globalSoong := dexpreopt.GetGlobalSoongConfig(ctx) 944 global := dexpreopt.GetGlobalConfig(ctx) 945 946 if global.DisableGenerateProfile || ctx.Config().UnbundledBuild() { 947 return nil 948 } 949 950 defaultProfile := "frameworks/base/config/boot-profile.txt" 951 bootFrameworkProfile := android.PathForSource(ctx, defaultProfile) 952 953 profile := image.dir.Join(ctx, "boot.bprof") 954 955 rule := android.NewRuleBuilder(pctx, ctx) 956 rule.Command(). 957 Text(`ANDROID_LOG_TAGS="*:e"`). 958 Tool(globalSoong.Profman). 959 Flag("--output-profile-type=bprof"). 960 FlagWithInput("--create-profile-from=", bootFrameworkProfile). 961 FlagForEachInput("--apk=", image.dexPathsDeps.Paths()). 962 FlagForEachArg("--dex-location=", image.getAnyAndroidVariant().dexLocationsDeps). 963 FlagWithOutput("--reference-profile-file=", profile) 964 965 rule.Install(profile, "/system/etc/boot-image.bprof") 966 rule.Build("bootFrameworkProfile", "profile boot framework jars") 967 image.profileInstalls = append(image.profileInstalls, rule.Installs()...) 968 image.profileLicenseMetadataFile = android.OptionalPathForPath(ctx.LicenseMetadataFile()) 969 970 return profile 971} 972 973func dumpOatRules(ctx android.ModuleContext, image *bootImageConfig) { 974 var allPhonies android.Paths 975 for _, image := range image.variants { 976 arch := image.target.Arch.ArchType 977 suffix := arch.String() 978 // Host and target might both use x86 arch. We need to ensure the names are unique. 979 if image.target.Os.Class == android.Host { 980 suffix = "host-" + suffix 981 } 982 // Create a rule to call oatdump. 983 output := android.PathForOutput(ctx, "boot."+suffix+".oatdump.txt") 984 rule := android.NewRuleBuilder(pctx, ctx) 985 imageLocationsOnHost, _ := image.imageLocations() 986 rule.Command(). 987 BuiltTool("oatdump"). 988 FlagWithInputList("--runtime-arg -Xbootclasspath:", image.dexPathsDeps.Paths(), ":"). 989 FlagWithList("--runtime-arg -Xbootclasspath-locations:", image.dexLocationsDeps, ":"). 990 FlagWithArg("--image=", strings.Join(imageLocationsOnHost, ":")).Implicits(image.imagesDeps.Paths()). 991 FlagWithOutput("--output=", output). 992 FlagWithArg("--instruction-set=", arch.String()) 993 rule.Build("dump-oat-boot-"+suffix, "dump oat boot "+arch.String()) 994 995 // Create a phony rule that depends on the output file and prints the path. 996 phony := android.PathForPhony(ctx, "dump-oat-boot-"+suffix) 997 rule = android.NewRuleBuilder(pctx, ctx) 998 rule.Command(). 999 Implicit(output). 1000 ImplicitOutput(phony). 1001 Text("echo").FlagWithArg("Output in ", output.String()) 1002 rule.Build("phony-dump-oat-boot-"+suffix, "dump oat boot "+arch.String()) 1003 1004 allPhonies = append(allPhonies, phony) 1005 } 1006 1007 phony := android.PathForPhony(ctx, "dump-oat-boot") 1008 ctx.Build(pctx, android.BuildParams{ 1009 Rule: android.Phony, 1010 Output: phony, 1011 Inputs: allPhonies, 1012 Description: "dump-oat-boot", 1013 }) 1014} 1015 1016func writeGlobalConfigForMake(ctx android.SingletonContext, path android.WritablePath) { 1017 data := dexpreopt.GetGlobalConfigRawData(ctx) 1018 1019 android.WriteFileRule(ctx, path, string(data)) 1020} 1021 1022// Define Make variables for boot image names, paths, etc. These variables are used in makefiles 1023// (make/core/dex_preopt_libart.mk) to generate install rules that copy boot image files to the 1024// correct output directories. 1025func (d *dexpreoptBootJars) MakeVars(ctx android.MakeVarsContext) { 1026 if d.dexpreoptConfigForMake != nil && !SkipDexpreoptBootJars(ctx) { 1027 ctx.Strict("DEX_PREOPT_CONFIG_FOR_MAKE", d.dexpreoptConfigForMake.String()) 1028 ctx.Strict("DEX_PREOPT_SOONG_CONFIG_FOR_MAKE", android.PathForOutput(ctx, "dexpreopt_soong.config").String()) 1029 } 1030 1031 image := d.defaultBootImage 1032 if image != nil { 1033 ctx.Strict("DEXPREOPT_IMAGE_PROFILE_BUILT_INSTALLED", image.profileInstalls.String()) 1034 if image.profileLicenseMetadataFile.Valid() { 1035 ctx.Strict("DEXPREOPT_IMAGE_PROFILE_LICENSE_METADATA", image.profileLicenseMetadataFile.String()) 1036 } 1037 1038 if SkipDexpreoptBootJars(ctx) { 1039 return 1040 } 1041 1042 global := dexpreopt.GetGlobalConfig(ctx) 1043 dexPaths, dexLocations := bcpForDexpreopt(ctx, global.PreoptWithUpdatableBcp) 1044 ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_FILES", strings.Join(dexPaths.Strings(), " ")) 1045 ctx.Strict("DEXPREOPT_BOOTCLASSPATH_DEX_LOCATIONS", strings.Join(dexLocations, " ")) 1046 1047 var imageNames []string 1048 // The primary ART boot image is exposed to Make for testing (gtests) and benchmarking 1049 // (golem) purposes. 1050 for _, current := range append(d.otherImages, image) { 1051 imageNames = append(imageNames, current.name) 1052 for _, variant := range current.variants { 1053 suffix := "" 1054 if variant.target.Os.Class == android.Host { 1055 suffix = "_host" 1056 } 1057 sfx := variant.name + suffix + "_" + variant.target.Arch.ArchType.String() 1058 ctx.Strict("DEXPREOPT_IMAGE_VDEX_BUILT_INSTALLED_"+sfx, variant.vdexInstalls.String()) 1059 ctx.Strict("DEXPREOPT_IMAGE_"+sfx, variant.imagePathOnHost.String()) 1060 ctx.Strict("DEXPREOPT_IMAGE_DEPS_"+sfx, strings.Join(variant.imagesDeps.Strings(), " ")) 1061 ctx.Strict("DEXPREOPT_IMAGE_BUILT_INSTALLED_"+sfx, variant.installs.String()) 1062 ctx.Strict("DEXPREOPT_IMAGE_UNSTRIPPED_BUILT_INSTALLED_"+sfx, variant.unstrippedInstalls.String()) 1063 if variant.licenseMetadataFile.Valid() { 1064 ctx.Strict("DEXPREOPT_IMAGE_LICENSE_METADATA_"+sfx, variant.licenseMetadataFile.String()) 1065 } 1066 } 1067 imageLocationsOnHost, imageLocationsOnDevice := current.getAnyAndroidVariant().imageLocations() 1068 ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_ON_HOST"+current.name, strings.Join(imageLocationsOnHost, ":")) 1069 ctx.Strict("DEXPREOPT_IMAGE_LOCATIONS_ON_DEVICE"+current.name, strings.Join(imageLocationsOnDevice, ":")) 1070 ctx.Strict("DEXPREOPT_IMAGE_ZIP_"+current.name, current.zip.String()) 1071 } 1072 // Ensure determinism. 1073 sort.Strings(imageNames) 1074 ctx.Strict("DEXPREOPT_IMAGE_NAMES", strings.Join(imageNames, " ")) 1075 } 1076} 1077