1// Copyright 2018 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// The dexpreopt package converts a global dexpreopt config and a module dexpreopt config into rules to perform 16// dexpreopting. 17// 18// It is used in two places; in the dexpeopt_gen binary for modules defined in Make, and directly linked into Soong. 19// 20// For Make modules it is built into the dexpreopt_gen binary, which is executed as a Make rule using global config and 21// module config specified in JSON files. The binary writes out two shell scripts, only updating them if they have 22// changed. One script takes an APK or JAR as an input and produces a zip file containing any outputs of preopting, 23// in the location they should be on the device. The Make build rules will unzip the zip file into $(PRODUCT_OUT) when 24// installing the APK, which will install the preopt outputs into $(PRODUCT_OUT)/system or $(PRODUCT_OUT)/system_other 25// as necessary. The zip file may be empty if preopting was disabled for any reason. 26// 27// The intermediate shell scripts allow changes to this package or to the global config to regenerate the shell scripts 28// but only require re-executing preopting if the script has changed. 29// 30// For Soong modules this package is linked directly into Soong and run from the java package. It generates the same 31// commands as for make, using athe same global config JSON file used by make, but using a module config structure 32// provided by Soong. The generated commands are then converted into Soong rule and written directly to the ninja file, 33// with no extra shell scripts involved. 34package dexpreopt 35 36import ( 37 "fmt" 38 "path/filepath" 39 "runtime" 40 "strings" 41 42 "android/soong/android" 43 44 "github.com/google/blueprint/pathtools" 45) 46 47const SystemPartition = "/system/" 48const SystemOtherPartition = "/system_other/" 49 50var DexpreoptRunningInSoong = false 51 52// GenerateDexpreoptRule generates a set of commands that will preopt a module based on a GlobalConfig and a 53// ModuleConfig. The produced files and their install locations will be available through rule.Installs(). 54func GenerateDexpreoptRule(ctx android.BuilderContext, globalSoong *GlobalSoongConfig, 55 global *GlobalConfig, module *ModuleConfig) (rule *android.RuleBuilder, err error) { 56 57 defer func() { 58 if r := recover(); r != nil { 59 if _, ok := r.(runtime.Error); ok { 60 panic(r) 61 } else if e, ok := r.(error); ok { 62 err = e 63 rule = nil 64 } else { 65 panic(r) 66 } 67 } 68 }() 69 70 rule = android.NewRuleBuilder(pctx, ctx) 71 72 generateProfile := module.ProfileClassListing.Valid() && !global.DisableGenerateProfile 73 generateBootProfile := module.ProfileBootListing.Valid() && !global.DisableGenerateProfile 74 75 var profile android.WritablePath 76 if generateProfile { 77 profile = profileCommand(ctx, globalSoong, global, module, rule) 78 } 79 if generateBootProfile { 80 bootProfileCommand(ctx, globalSoong, global, module, rule) 81 } 82 83 if !dexpreoptDisabled(ctx, global, module) { 84 if valid, err := validateClassLoaderContext(module.ClassLoaderContexts); err != nil { 85 android.ReportPathErrorf(ctx, err.Error()) 86 } else if valid { 87 fixClassLoaderContext(module.ClassLoaderContexts) 88 89 appImage := (generateProfile || module.ForceCreateAppImage || global.DefaultAppImages) && 90 !module.NoCreateAppImage 91 92 generateDM := shouldGenerateDM(module, global) 93 94 for archIdx, _ := range module.Archs { 95 dexpreoptCommand(ctx, globalSoong, global, module, rule, archIdx, profile, appImage, generateDM) 96 } 97 } 98 } 99 100 return rule, nil 101} 102 103func dexpreoptDisabled(ctx android.PathContext, global *GlobalConfig, module *ModuleConfig) bool { 104 if contains(global.DisablePreoptModules, module.Name) { 105 return true 106 } 107 108 // Don't preopt individual boot jars, they will be preopted together. 109 if global.BootJars.ContainsJar(module.Name) { 110 return true 111 } 112 113 // Don't preopt system server jars that are updatable. 114 if global.UpdatableSystemServerJars.ContainsJar(module.Name) { 115 return true 116 } 117 118 // If OnlyPreoptBootImageAndSystemServer=true and module is not in boot class path skip 119 // Also preopt system server jars since selinux prevents system server from loading anything from 120 // /data. If we don't do this they will need to be extracted which is not favorable for RAM usage 121 // or performance. If PreoptExtractedApk is true, we ignore the only preopt boot image options. 122 if global.OnlyPreoptBootImageAndSystemServer && !global.BootJars.ContainsJar(module.Name) && 123 !global.SystemServerJars.ContainsJar(module.Name) && !module.PreoptExtractedApk { 124 return true 125 } 126 127 return false 128} 129 130func profileCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig, 131 module *ModuleConfig, rule *android.RuleBuilder) android.WritablePath { 132 133 profilePath := module.BuildPath.InSameDir(ctx, "profile.prof") 134 profileInstalledPath := module.DexLocation + ".prof" 135 136 if !module.ProfileIsTextListing { 137 rule.Command().Text("rm -f").Output(profilePath) 138 rule.Command().Text("touch").Output(profilePath) 139 } 140 141 cmd := rule.Command(). 142 Text(`ANDROID_LOG_TAGS="*:e"`). 143 Tool(globalSoong.Profman) 144 145 if module.ProfileIsTextListing { 146 // The profile is a test listing of classes (used for framework jars). 147 // We need to generate the actual binary profile before being able to compile. 148 cmd.FlagWithInput("--create-profile-from=", module.ProfileClassListing.Path()) 149 } else { 150 // The profile is binary profile (used for apps). Run it through profman to 151 // ensure the profile keys match the apk. 152 cmd. 153 Flag("--copy-and-update-profile-key"). 154 FlagWithInput("--profile-file=", module.ProfileClassListing.Path()) 155 } 156 157 cmd. 158 Flag("--output-profile-type=app"). 159 FlagWithInput("--apk=", module.DexPath). 160 Flag("--dex-location="+module.DexLocation). 161 FlagWithOutput("--reference-profile-file=", profilePath) 162 163 if !module.ProfileIsTextListing { 164 cmd.Text(fmt.Sprintf(`|| echo "Profile out of date for %s"`, module.DexPath)) 165 } 166 rule.Install(profilePath, profileInstalledPath) 167 168 return profilePath 169} 170 171func bootProfileCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig, 172 module *ModuleConfig, rule *android.RuleBuilder) android.WritablePath { 173 174 profilePath := module.BuildPath.InSameDir(ctx, "profile.bprof") 175 profileInstalledPath := module.DexLocation + ".bprof" 176 177 if !module.ProfileIsTextListing { 178 rule.Command().Text("rm -f").Output(profilePath) 179 rule.Command().Text("touch").Output(profilePath) 180 } 181 182 cmd := rule.Command(). 183 Text(`ANDROID_LOG_TAGS="*:e"`). 184 Tool(globalSoong.Profman) 185 186 // The profile is a test listing of methods. 187 // We need to generate the actual binary profile. 188 cmd.FlagWithInput("--create-profile-from=", module.ProfileBootListing.Path()) 189 190 cmd. 191 Flag("--output-profile-type=bprof"). 192 FlagWithInput("--apk=", module.DexPath). 193 Flag("--dex-location="+module.DexLocation). 194 FlagWithOutput("--reference-profile-file=", profilePath) 195 196 if !module.ProfileIsTextListing { 197 cmd.Text(fmt.Sprintf(`|| echo "Profile out of date for %s"`, module.DexPath)) 198 } 199 rule.Install(profilePath, profileInstalledPath) 200 201 return profilePath 202} 203 204func dexpreoptCommand(ctx android.PathContext, globalSoong *GlobalSoongConfig, global *GlobalConfig, 205 module *ModuleConfig, rule *android.RuleBuilder, archIdx int, profile android.WritablePath, 206 appImage bool, generateDM bool) { 207 208 arch := module.Archs[archIdx] 209 210 // HACK: make soname in Soong-generated .odex files match Make. 211 base := filepath.Base(module.DexLocation) 212 if filepath.Ext(base) == ".jar" { 213 base = "javalib.jar" 214 } else if filepath.Ext(base) == ".apk" { 215 base = "package.apk" 216 } 217 218 toOdexPath := func(path string) string { 219 return filepath.Join( 220 filepath.Dir(path), 221 "oat", 222 arch.String(), 223 pathtools.ReplaceExtension(filepath.Base(path), "odex")) 224 } 225 226 odexPath := module.BuildPath.InSameDir(ctx, "oat", arch.String(), pathtools.ReplaceExtension(base, "odex")) 227 odexInstallPath := toOdexPath(module.DexLocation) 228 if odexOnSystemOther(module, global) { 229 odexInstallPath = filepath.Join(SystemOtherPartition, odexInstallPath) 230 } 231 232 vdexPath := odexPath.ReplaceExtension(ctx, "vdex") 233 vdexInstallPath := pathtools.ReplaceExtension(odexInstallPath, "vdex") 234 235 invocationPath := odexPath.ReplaceExtension(ctx, "invocation") 236 237 systemServerJars := NonUpdatableSystemServerJars(ctx, global) 238 239 rule.Command().FlagWithArg("mkdir -p ", filepath.Dir(odexPath.String())) 240 rule.Command().FlagWithOutput("rm -f ", odexPath) 241 242 if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 { 243 // System server jars should be dexpreopted together: class loader context of each jar 244 // should include all preceding jars on the system server classpath. 245 246 var clcHost android.Paths 247 var clcTarget []string 248 for _, lib := range systemServerJars[:jarIndex] { 249 clcHost = append(clcHost, SystemServerDexJarHostPath(ctx, lib)) 250 clcTarget = append(clcTarget, filepath.Join("/system/framework", lib+".jar")) 251 } 252 253 // Copy the system server jar to a predefined location where dex2oat will find it. 254 dexPathHost := SystemServerDexJarHostPath(ctx, module.Name) 255 rule.Command().Text("mkdir -p").Flag(filepath.Dir(dexPathHost.String())) 256 rule.Command().Text("cp -f").Input(module.DexPath).Output(dexPathHost) 257 258 checkSystemServerOrder(ctx, jarIndex) 259 260 rule.Command(). 261 Text("class_loader_context_arg=--class-loader-context=PCL[" + strings.Join(clcHost.Strings(), ":") + "]"). 262 Implicits(clcHost). 263 Text("stored_class_loader_context_arg=--stored-class-loader-context=PCL[" + strings.Join(clcTarget, ":") + "]") 264 265 } else { 266 // There are three categories of Java modules handled here: 267 // 268 // - Modules that have passed verify_uses_libraries check. They are AOT-compiled and 269 // expected to be loaded on device without CLC mismatch errors. 270 // 271 // - Modules that have failed the check in relaxed mode, so it didn't cause a build error. 272 // They are dexpreopted with "verify" filter and not AOT-compiled. 273 // TODO(b/132357300): ensure that CLC mismatch errors are ignored with "verify" filter. 274 // 275 // - Modules that didn't run the check. They are AOT-compiled, but it's unknown if they 276 // will have CLC mismatch errors on device (the check is disabled by default). 277 // 278 // TODO(b/132357300): enable the check by default and eliminate the last category, so that 279 // no time/space is wasted on AOT-compiling modules that will fail CLC check on device. 280 281 var manifestOrApk android.Path 282 if module.ManifestPath.Valid() { 283 // Ok, there is an XML manifest. 284 manifestOrApk = module.ManifestPath.Path() 285 } else if filepath.Ext(base) == ".apk" { 286 // Ok, there is is an APK with the manifest inside. 287 manifestOrApk = module.DexPath 288 } 289 290 // Generate command that saves target SDK version in a shell variable. 291 if manifestOrApk == nil { 292 // There is neither an XML manifest nor APK => nowhere to extract targetSdkVersion from. 293 // Set the latest ("any") version: then construct_context will not add any compatibility 294 // libraries (if this is incorrect, there will be a CLC mismatch and dexopt on device). 295 rule.Command().Textf(`target_sdk_version=%d`, AnySdkVersion) 296 } else { 297 rule.Command().Text(`target_sdk_version="$(`). 298 Tool(globalSoong.ManifestCheck). 299 Flag("--extract-target-sdk-version"). 300 Input(manifestOrApk). 301 FlagWithInput("--aapt ", globalSoong.Aapt). 302 Text(`)"`) 303 } 304 305 // Generate command that saves host and target class loader context in shell variables. 306 clc, paths := ComputeClassLoaderContext(module.ClassLoaderContexts) 307 rule.Command(). 308 Text(`eval "$(`).Tool(globalSoong.ConstructContext). 309 Text(` --target-sdk-version ${target_sdk_version}`). 310 Text(clc).Implicits(paths). 311 Text(`)"`) 312 } 313 314 // Devices that do not have a product partition use a symlink from /product to /system/product. 315 // Because on-device dexopt will see dex locations starting with /product, we change the paths 316 // to mimic this behavior. 317 dexLocationArg := module.DexLocation 318 if strings.HasPrefix(dexLocationArg, "/system/product/") { 319 dexLocationArg = strings.TrimPrefix(dexLocationArg, "/system") 320 } 321 322 cmd := rule.Command(). 323 Text(`ANDROID_LOG_TAGS="*:e"`). 324 Tool(globalSoong.Dex2oat). 325 Flag("--avoid-storing-invocation"). 326 FlagWithOutput("--write-invocation-to=", invocationPath).ImplicitOutput(invocationPath). 327 Flag("--runtime-arg").FlagWithArg("-Xms", global.Dex2oatXms). 328 Flag("--runtime-arg").FlagWithArg("-Xmx", global.Dex2oatXmx). 329 Flag("--runtime-arg").FlagWithInputList("-Xbootclasspath:", module.PreoptBootClassPathDexFiles, ":"). 330 Flag("--runtime-arg").FlagWithList("-Xbootclasspath-locations:", module.PreoptBootClassPathDexLocations, ":"). 331 Flag("${class_loader_context_arg}"). 332 Flag("${stored_class_loader_context_arg}"). 333 FlagWithArg("--boot-image=", strings.Join(module.DexPreoptImageLocationsOnHost, ":")).Implicits(module.DexPreoptImagesDeps[archIdx].Paths()). 334 FlagWithInput("--dex-file=", module.DexPath). 335 FlagWithArg("--dex-location=", dexLocationArg). 336 FlagWithOutput("--oat-file=", odexPath).ImplicitOutput(vdexPath). 337 // Pass an empty directory, dex2oat shouldn't be reading arbitrary files 338 FlagWithArg("--android-root=", global.EmptyDirectory). 339 FlagWithArg("--instruction-set=", arch.String()). 340 FlagWithArg("--instruction-set-variant=", global.CpuVariant[arch]). 341 FlagWithArg("--instruction-set-features=", global.InstructionSetFeatures[arch]). 342 Flag("--no-generate-debug-info"). 343 Flag("--generate-build-id"). 344 Flag("--abort-on-hard-verifier-error"). 345 Flag("--force-determinism"). 346 FlagWithArg("--no-inline-from=", "core-oj.jar") 347 348 var preoptFlags []string 349 if len(module.PreoptFlags) > 0 { 350 preoptFlags = module.PreoptFlags 351 } else if len(global.PreoptFlags) > 0 { 352 preoptFlags = global.PreoptFlags 353 } 354 355 if len(preoptFlags) > 0 { 356 cmd.Text(strings.Join(preoptFlags, " ")) 357 } 358 359 if module.UncompressedDex { 360 cmd.FlagWithArg("--copy-dex-files=", "false") 361 } 362 363 if !android.PrefixInList(preoptFlags, "--compiler-filter=") { 364 var compilerFilter string 365 if global.SystemServerJars.ContainsJar(module.Name) { 366 // Jars of system server, use the product option if it is set, speed otherwise. 367 if global.SystemServerCompilerFilter != "" { 368 compilerFilter = global.SystemServerCompilerFilter 369 } else { 370 compilerFilter = "speed" 371 } 372 } else if contains(global.SpeedApps, module.Name) || contains(global.SystemServerApps, module.Name) { 373 // Apps loaded into system server, and apps the product default to being compiled with the 374 // 'speed' compiler filter. 375 compilerFilter = "speed" 376 } else if profile != nil { 377 // For non system server jars, use speed-profile when we have a profile. 378 compilerFilter = "speed-profile" 379 } else if global.DefaultCompilerFilter != "" { 380 compilerFilter = global.DefaultCompilerFilter 381 } else { 382 compilerFilter = "quicken" 383 } 384 if module.EnforceUsesLibraries { 385 // If the verify_uses_libraries check failed (in this case status file contains a 386 // non-empty error message), then use "verify" compiler filter to avoid compiling any 387 // code (it would be rejected on device because of a class loader context mismatch). 388 cmd.Text("--compiler-filter=$(if test -s "). 389 Input(module.EnforceUsesLibrariesStatusFile). 390 Text(" ; then echo verify ; else echo " + compilerFilter + " ; fi)") 391 } else { 392 cmd.FlagWithArg("--compiler-filter=", compilerFilter) 393 } 394 } 395 396 if generateDM { 397 cmd.FlagWithArg("--copy-dex-files=", "false") 398 dmPath := module.BuildPath.InSameDir(ctx, "generated.dm") 399 dmInstalledPath := pathtools.ReplaceExtension(module.DexLocation, "dm") 400 tmpPath := module.BuildPath.InSameDir(ctx, "primary.vdex") 401 rule.Command().Text("cp -f").Input(vdexPath).Output(tmpPath) 402 rule.Command().Tool(globalSoong.SoongZip). 403 FlagWithArg("-L", "9"). 404 FlagWithOutput("-o", dmPath). 405 Flag("-j"). 406 Input(tmpPath) 407 rule.Install(dmPath, dmInstalledPath) 408 } 409 410 // By default, emit debug info. 411 debugInfo := true 412 if global.NoDebugInfo { 413 // If the global setting suppresses mini-debug-info, disable it. 414 debugInfo = false 415 } 416 417 // PRODUCT_SYSTEM_SERVER_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO. 418 // PRODUCT_OTHER_JAVA_DEBUG_INFO overrides WITH_DEXPREOPT_DEBUG_INFO. 419 if global.SystemServerJars.ContainsJar(module.Name) { 420 if global.AlwaysSystemServerDebugInfo { 421 debugInfo = true 422 } else if global.NeverSystemServerDebugInfo { 423 debugInfo = false 424 } 425 } else { 426 if global.AlwaysOtherDebugInfo { 427 debugInfo = true 428 } else if global.NeverOtherDebugInfo { 429 debugInfo = false 430 } 431 } 432 433 // Never enable on eng. 434 if global.IsEng { 435 debugInfo = false 436 } 437 438 if debugInfo { 439 cmd.Flag("--generate-mini-debug-info") 440 } else { 441 cmd.Flag("--no-generate-mini-debug-info") 442 } 443 444 // Set the compiler reason to 'prebuilt' to identify the oat files produced 445 // during the build, as opposed to compiled on the device. 446 cmd.FlagWithArg("--compilation-reason=", "prebuilt") 447 448 if appImage { 449 appImagePath := odexPath.ReplaceExtension(ctx, "art") 450 appImageInstallPath := pathtools.ReplaceExtension(odexInstallPath, "art") 451 cmd.FlagWithOutput("--app-image-file=", appImagePath). 452 FlagWithArg("--image-format=", "lz4") 453 if !global.DontResolveStartupStrings { 454 cmd.FlagWithArg("--resolve-startup-const-strings=", "true") 455 } 456 rule.Install(appImagePath, appImageInstallPath) 457 } 458 459 if profile != nil { 460 cmd.FlagWithInput("--profile-file=", profile) 461 } 462 463 rule.Install(odexPath, odexInstallPath) 464 rule.Install(vdexPath, vdexInstallPath) 465} 466 467func shouldGenerateDM(module *ModuleConfig, global *GlobalConfig) bool { 468 // Generating DM files only makes sense for verify, avoid doing for non verify compiler filter APKs. 469 // No reason to use a dm file if the dex is already uncompressed. 470 return global.GenerateDMFiles && !module.UncompressedDex && 471 contains(module.PreoptFlags, "--compiler-filter=verify") 472} 473 474func OdexOnSystemOtherByName(name string, dexLocation string, global *GlobalConfig) bool { 475 if !global.HasSystemOther { 476 return false 477 } 478 479 if global.SanitizeLite { 480 return false 481 } 482 483 if contains(global.SpeedApps, name) || contains(global.SystemServerApps, name) { 484 return false 485 } 486 487 for _, f := range global.PatternsOnSystemOther { 488 if makefileMatch(filepath.Join(SystemPartition, f), dexLocation) { 489 return true 490 } 491 } 492 493 return false 494} 495 496func odexOnSystemOther(module *ModuleConfig, global *GlobalConfig) bool { 497 return OdexOnSystemOtherByName(module.Name, module.DexLocation, global) 498} 499 500// PathToLocation converts .../system/framework/arm64/boot.art to .../system/framework/boot.art 501func PathToLocation(path android.Path, arch android.ArchType) string { 502 return PathStringToLocation(path.String(), arch) 503} 504 505// PathStringToLocation converts .../system/framework/arm64/boot.art to .../system/framework/boot.art 506func PathStringToLocation(path string, arch android.ArchType) string { 507 pathArch := filepath.Base(filepath.Dir(path)) 508 if pathArch != arch.String() { 509 panic(fmt.Errorf("last directory in %q must be %q", path, arch.String())) 510 } 511 return filepath.Join(filepath.Dir(filepath.Dir(path)), filepath.Base(path)) 512} 513 514func makefileMatch(pattern, s string) bool { 515 percent := strings.IndexByte(pattern, '%') 516 switch percent { 517 case -1: 518 return pattern == s 519 case len(pattern) - 1: 520 return strings.HasPrefix(s, pattern[:len(pattern)-1]) 521 default: 522 panic(fmt.Errorf("unsupported makefile pattern %q", pattern)) 523 } 524} 525 526var nonUpdatableSystemServerJarsKey = android.NewOnceKey("nonUpdatableSystemServerJars") 527 528// TODO: eliminate the superficial global config parameter by moving global config definition 529// from java subpackage to dexpreopt. 530func NonUpdatableSystemServerJars(ctx android.PathContext, global *GlobalConfig) []string { 531 return ctx.Config().Once(nonUpdatableSystemServerJarsKey, func() interface{} { 532 return android.RemoveListFromList(global.SystemServerJars.CopyOfJars(), global.UpdatableSystemServerJars.CopyOfJars()) 533 }).([]string) 534} 535 536// A predefined location for the system server dex jars. This is needed in order to generate 537// class loader context for dex2oat, as the path to the jar in the Soong module may be unknown 538// at that time (Soong processes the jars in dependency order, which may be different from the 539// the system server classpath order). 540func SystemServerDexJarHostPath(ctx android.PathContext, jar string) android.OutputPath { 541 if DexpreoptRunningInSoong { 542 // Soong module, just use the default output directory $OUT/soong. 543 return android.PathForOutput(ctx, "system_server_dexjars", jar+".jar") 544 } else { 545 // Make module, default output directory is $OUT (passed via the "null config" created 546 // by dexpreopt_gen). Append Soong subdirectory to match Soong module paths. 547 return android.PathForOutput(ctx, "soong", "system_server_dexjars", jar+".jar") 548 } 549} 550 551// Check the order of jars on the system server classpath and give a warning/error if a jar precedes 552// one of its dependencies. This is not an error, but a missed optimization, as dexpreopt won't 553// have the dependency jar in the class loader context, and it won't be able to resolve any 554// references to its classes and methods. 555func checkSystemServerOrder(ctx android.PathContext, jarIndex int) { 556 mctx, isModule := ctx.(android.ModuleContext) 557 if isModule { 558 config := GetGlobalConfig(ctx) 559 jars := NonUpdatableSystemServerJars(ctx, config) 560 mctx.WalkDeps(func(dep android.Module, parent android.Module) bool { 561 depIndex := android.IndexList(dep.Name(), jars) 562 if jarIndex < depIndex && !config.BrokenSuboptimalOrderOfSystemServerJars { 563 jar := jars[jarIndex] 564 dep := jars[depIndex] 565 mctx.ModuleErrorf("non-optimal order of jars on the system server classpath:"+ 566 " '%s' precedes its dependency '%s', so dexpreopt is unable to resolve any"+ 567 " references from '%s' to '%s'.\n", jar, dep, jar, dep) 568 } 569 return true 570 }) 571 } 572} 573 574// Returns path to a file containing the reult of verify_uses_libraries check (empty if the check 575// has succeeded, or an error message if it failed). 576func UsesLibrariesStatusFile(ctx android.ModuleContext) android.WritablePath { 577 return android.PathForModuleOut(ctx, "enforce_uses_libraries.status") 578} 579 580func contains(l []string, s string) bool { 581 for _, e := range l { 582 if e == s { 583 return true 584 } 585 } 586 return false 587} 588 589var copyOf = android.CopyOf 590