1// Copyright 2020 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 dexpreopt 16 17import ( 18 "encoding/json" 19 "fmt" 20 "sort" 21 "strconv" 22 "strings" 23 24 "android/soong/android" 25) 26 27// This comment describes the following: 28// 1. the concept of class loader context (CLC) and its relation to classpath 29// 2. how PackageManager constructs CLC from shared libraries and their dependencies 30// 3. build-time vs. run-time CLC and why this matters for dexpreopt 31// 4. manifest fixer: a tool that adds missing <uses-library> tags to the manifests 32// 5. build system support for CLC 33// 34// 1. Class loader context 35// ----------------------- 36// 37// Java libraries and apps that have run-time dependency on other libraries should list the used 38// libraries in their manifest (AndroidManifest.xml file). Each used library should be specified in 39// a <uses-library> tag that has the library name and an optional attribute specifying if the 40// library is optional or required. Required libraries are necessary for the library/app to run (it 41// will fail at runtime if the library cannot be loaded), and optional libraries are used only if 42// they are present (if not, the library/app can run without them). 43// 44// The libraries listed in <uses-library> tags are in the classpath of a library/app. 45// 46// Besides libraries, an app may also use another APK (for example in the case of split APKs), or 47// anything that gets added by the app dynamically. In general, it is impossible to know at build 48// time what the app may use at runtime. In the build system we focus on the known part: libraries. 49// 50// Class loader context (CLC) is a tree-like structure that describes class loader hierarchy. The 51// build system uses CLC in a more narrow sense: it is a tree of libraries that represents 52// transitive closure of all <uses-library> dependencies of a library/app. The top-level elements of 53// a CLC are the direct <uses-library> dependencies specified in the manifest (aka. classpath). Each 54// node of a CLC tree is a <uses-library> which may have its own <uses-library> sub-nodes. 55// 56// Because <uses-library> dependencies are, in general, a graph and not necessarily a tree, CLC may 57// contain subtrees for the same library multiple times. In other words, CLC is the dependency graph 58// "unfolded" to a tree. The duplication is only on a logical level, and the actual underlying class 59// loaders are not duplicated (at runtime there is a single class loader instance for each library). 60// 61// Example: A has <uses-library> tags B, C and D; C has <uses-library tags> B and D; 62// D has <uses-library> E; B and E have no <uses-library> dependencies. The CLC is: 63// A 64// ├── B 65// ├── C 66// │ ├── B 67// │ └── D 68// │ └── E 69// └── D 70// └── E 71// 72// CLC defines the lookup order of libraries when resolving Java classes used by the library/app. 73// The lookup order is important because libraries may contain duplicate classes, and the class is 74// resolved to the first match. 75// 76// 2. PackageManager and "shared" libraries 77// ---------------------------------------- 78// 79// In order to load an APK at runtime, PackageManager (in frameworks/base) creates a CLC. It adds 80// the libraries listed in the <uses-library> tags in the app's manifest as top-level CLC elements. 81// For each of the used libraries PackageManager gets all its <uses-library> dependencies (specified 82// as tags in the manifest of that library) and adds a nested CLC for each dependency. This process 83// continues recursively until all leaf nodes of the constructed CLC tree are libraries that have no 84// <uses-library> dependencies. 85// 86// PackageManager is aware only of "shared" libraries. The definition of "shared" here differs from 87// its usual meaning (as in shared vs. static). In Android, Java "shared" libraries are those listed 88// in /system/etc/permissions/platform.xml file. This file is installed on device. Each entry in it 89// contains the name of a "shared" library, a path to its DEX jar file and a list of dependencies 90// (other "shared" libraries that this one uses at runtime and specifies them in <uses-library> tags 91// in its manifest). 92// 93// In other words, there are two sources of information that allow PackageManager to construct CLC 94// at runtime: <uses-library> tags in the manifests and "shared" library dependencies in 95// /system/etc/permissions/platform.xml. 96// 97// 3. Build-time and run-time CLC and dexpreopt 98// -------------------------------------------- 99// 100// CLC is needed not only when loading a library/app, but also when compiling it. Compilation may 101// happen either on device (known as "dexopt") or during the build (known as "dexpreopt"). Since 102// dexopt takes place on device, it has the same information as PackageManager (manifests and 103// shared library dependencies). Dexpreopt, on the other hand, takes place on host and in a totally 104// different environment, and it has to get the same information from the build system (see the 105// section about build system support below). 106// 107// Thus, the build-time CLC used by dexpreopt and the run-time CLC used by PackageManager are 108// the same thing, but computed in two different ways. 109// 110// It is important that build-time and run-time CLCs coincide, otherwise the AOT-compiled code 111// created by dexpreopt will be rejected. In order to check the equality of build-time and 112// run-time CLCs, the dex2oat compiler records build-time CLC in the *.odex files (in the 113// "classpath" field of the OAT file header). To find the stored CLC, use the following command: 114// `oatdump --oat-file=<FILE> | grep '^classpath = '`. 115// 116// Mismatch between build-time and run-time CLC is reported in logcat during boot (search with 117// `logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch'`. Mismatch is bad for performance, as it 118// forces the library/app to either be dexopted, or to run without any optimizations (e.g. the app's 119// code may need to be extracted in memory from the APK, a very expensive operation). 120// 121// A <uses-library> can be either optional or required. From dexpreopt standpoint, required library 122// must be present at build time (its absence is a build error). An optional library may be either 123// present or absent at build time: if present, it will be added to the CLC, passed to dex2oat and 124// recorded in the *.odex file; otherwise, if the library is absent, it will be skipped and not 125// added to CLC. If there is a mismatch between built-time and run-time status (optional library is 126// present in one case, but not the other), then the build-time and run-time CLCs won't match and 127// the compiled code will be rejected. It is unknown at build time if the library will be present at 128// runtime, therefore either including or excluding it may cause CLC mismatch. 129// 130// 4. Manifest fixer 131// ----------------- 132// 133// Sometimes <uses-library> tags are missing from the source manifest of a library/app. This may 134// happen for example if one of the transitive dependencies of the library/app starts using another 135// <uses-library>, and the library/app's manifest isn't updated to include it. 136// 137// Soong can compute some of the missing <uses-library> tags for a given library/app automatically 138// as SDK libraries in the transitive dependency closure of the library/app. The closure is needed 139// because a library/app may depend on a static library that may in turn depend on an SDK library, 140// (possibly transitively via another library). 141// 142// Not all <uses-library> tags can be computed in this way, because some of the <uses-library> 143// dependencies are not SDK libraries, or they are not reachable via transitive dependency closure. 144// But when possible, allowing Soong to calculate the manifest entries is less prone to errors and 145// simplifies maintenance. For example, consider a situation when many apps use some static library 146// that adds a new <uses-library> dependency -- all the apps will have to be updated. That is 147// difficult to maintain. 148// 149// Soong computes the libraries that need to be in the manifest as the top-level libraries in CLC. 150// These libraries are passed to the manifest_fixer. 151// 152// All libraries added to the manifest should be "shared" libraries, so that PackageManager can look 153// up their dependencies and reconstruct the nested subcontexts at runtime. There is no build check 154// to ensure this, it is an assumption. 155// 156// 5. Build system support 157// ----------------------- 158// 159// In order to construct CLC for dexpreopt and manifest_fixer, the build system needs to know all 160// <uses-library> dependencies of the dexpreopted library/app (including transitive dependencies). 161// For each <uses-librarry> dependency it needs to know the following information: 162// 163// - the real name of the <uses-library> (it may be different from the module name) 164// - build-time (on host) and run-time (on device) paths to the DEX jar file of the library 165// - whether this library is optional or required 166// - all <uses-library> dependencies 167// 168// Since the build system doesn't have access to the manifest contents (it cannot read manifests at 169// the time of build rule generation), it is necessary to copy this information to the Android.bp 170// and Android.mk files. For blueprints, the relevant properties are `uses_libs` and 171// `optional_uses_libs`. For makefiles, relevant variables are `LOCAL_USES_LIBRARIES` and 172// `LOCAL_OPTIONAL_USES_LIBRARIES`. It is preferable to avoid specifying these properties explicilty 173// when they can be computed automatically by Soong (as the transitive closure of SDK library 174// dependencies). 175// 176// Some of the Java libraries that are used as <uses-library> are not SDK libraries (they are 177// defined as `java_library` rather than `java_sdk_library` in the Android.bp files). In order for 178// the build system to handle them automatically like SDK libraries, it is possible to set a 179// property `provides_uses_lib` or variable `LOCAL_PROVIDES_USES_LIBRARY` on the blueprint/makefile 180// module of such library. This property can also be used to specify real library name in cases 181// when it differs from the module name. 182// 183// Because the information from the manifests has to be duplicated in the Android.bp/Android.mk 184// files, there is a danger that it may get out of sync. To guard against that, the build system 185// generates a rule that checks the metadata in the build files against the contents of a manifest 186// (verify_uses_libraries). The manifest can be available as a source file, or as part of a prebuilt 187// APK. Note that reading the manifests at the Ninja stage of the build is fine, unlike the build 188// rule generation phase. 189// 190// ClassLoaderContext is a structure that represents CLC. 191// 192type ClassLoaderContext struct { 193 // The name of the library. 194 Name string 195 196 // If the library is optional or required. 197 Optional bool 198 199 // If the library is implicitly infered by Soong (as opposed to explicitly added via `uses_libs` 200 // or `optional_uses_libs`. 201 Implicit bool 202 203 // On-host build path to the library dex file (used in dex2oat argument --class-loader-context). 204 Host android.Path 205 206 // On-device install path (used in dex2oat argument --stored-class-loader-context). 207 Device string 208 209 // Nested sub-CLC for dependencies. 210 Subcontexts []*ClassLoaderContext 211} 212 213// excludeLibs excludes the libraries from this ClassLoaderContext. 214// 215// This treats the supplied context as being immutable (as it may come from a dependency). So, it 216// implements copy-on-exclusion logic. That means that if any of the excluded libraries are used 217// within this context then this will return a deep copy of this without those libraries. 218// 219// If this ClassLoaderContext matches one of the libraries to exclude then this returns (nil, true) 220// to indicate that this context should be excluded from the containing list. 221// 222// If any of this ClassLoaderContext's Subcontexts reference the excluded libraries then this 223// returns a pointer to a copy of this without the excluded libraries and true to indicate that this 224// was copied. 225// 226// Otherwise, this returns a pointer to this and false to indicate that this was not copied. 227func (c *ClassLoaderContext) excludeLibs(excludedLibs []string) (*ClassLoaderContext, bool) { 228 if android.InList(c.Name, excludedLibs) { 229 return nil, true 230 } 231 232 if excludedList, modified := excludeLibsFromCLCList(c.Subcontexts, excludedLibs); modified { 233 clcCopy := *c 234 clcCopy.Subcontexts = excludedList 235 return &clcCopy, true 236 } 237 238 return c, false 239} 240 241// ClassLoaderContextMap is a map from SDK version to CLC. There is a special entry with key 242// AnySdkVersion that stores unconditional CLC that is added regardless of the target SDK version. 243// 244// Conditional CLC is for compatibility libraries which didn't exist prior to a certain SDK version 245// (say, N), but classes in them were in the bootclasspath jars, etc., and in version N they have 246// been separated into a standalone <uses-library>. Compatibility libraries should only be in the 247// CLC if the library/app that uses them has `targetSdkVersion` less than N in the manifest. 248// 249// Currently only apps (but not libraries) use conditional CLC. 250// 251// Target SDK version information is unavailable to the build system at rule generation time, so 252// the build system doesn't know whether conditional CLC is needed for a given app or not. So it 253// generates a build rule that includes conditional CLC for all versions, extracts the target SDK 254// version from the manifest, and filters the CLCs based on that version. Exact final CLC that is 255// passed to dex2oat is unknown to the build system, and gets known only at Ninja stage. 256// 257type ClassLoaderContextMap map[int][]*ClassLoaderContext 258 259// Compatibility libraries. Some are optional, and some are required: this is the default that 260// affects how they are handled by the Soong logic that automatically adds implicit SDK libraries 261// to the manifest_fixer, but an explicit `uses_libs`/`optional_uses_libs` can override this. 262var OrgApacheHttpLegacy = "org.apache.http.legacy" 263var AndroidTestBase = "android.test.base" 264var AndroidTestMock = "android.test.mock" 265var AndroidHidlBase = "android.hidl.base-V1.0-java" 266var AndroidHidlManager = "android.hidl.manager-V1.0-java" 267 268// Compatibility libraries grouped by version/optionality (for convenience, to avoid repeating the 269// same lists in multiple places). 270var OptionalCompatUsesLibs28 = []string{ 271 OrgApacheHttpLegacy, 272} 273var OptionalCompatUsesLibs30 = []string{ 274 AndroidTestBase, 275 AndroidTestMock, 276} 277var CompatUsesLibs29 = []string{ 278 AndroidHidlManager, 279 AndroidHidlBase, 280} 281var OptionalCompatUsesLibs = append(android.CopyOf(OptionalCompatUsesLibs28), OptionalCompatUsesLibs30...) 282var CompatUsesLibs = android.CopyOf(CompatUsesLibs29) 283 284const UnknownInstallLibraryPath = "error" 285 286// AnySdkVersion means that the class loader context is needed regardless of the targetSdkVersion 287// of the app. The numeric value affects the key order in the map and, as a result, the order of 288// arguments passed to construct_context.py (high value means that the unconditional context goes 289// last). We use the converntional "current" SDK level (10000), but any big number would do as well. 290const AnySdkVersion int = android.FutureApiLevelInt 291 292// Add class loader context for the given library to the map entry for the given SDK version. 293func (clcMap ClassLoaderContextMap) addContext(ctx android.ModuleInstallPathContext, sdkVer int, 294 lib string, optional, implicit bool, hostPath, installPath android.Path, 295 nestedClcMap ClassLoaderContextMap) error { 296 297 // For prebuilts, library should have the same name as the source module. 298 lib = android.RemoveOptionalPrebuiltPrefix(lib) 299 300 devicePath := UnknownInstallLibraryPath 301 if installPath == nil { 302 if android.InList(lib, CompatUsesLibs) || android.InList(lib, OptionalCompatUsesLibs) { 303 // Assume that compatibility libraries are installed in /system/framework. 304 installPath = android.PathForModuleInstall(ctx, "framework", lib+".jar") 305 } else { 306 // For some stub libraries the only known thing is the name of their implementation 307 // library, but the library itself is unavailable (missing or part of a prebuilt). In 308 // such cases we still need to add the library to <uses-library> tags in the manifest, 309 // but we cannot use it for dexpreopt. 310 } 311 } 312 if installPath != nil { 313 devicePath = android.InstallPathToOnDevicePath(ctx, installPath.(android.InstallPath)) 314 } 315 316 // Nested class loader context shouldn't have conditional part (it is allowed only at the top level). 317 for ver, _ := range nestedClcMap { 318 if ver != AnySdkVersion { 319 clcStr, _ := ComputeClassLoaderContext(nestedClcMap) 320 return fmt.Errorf("nested class loader context shouldn't have conditional part: %s", clcStr) 321 } 322 } 323 subcontexts := nestedClcMap[AnySdkVersion] 324 325 // Check if the library with this name is already present in unconditional top-level CLC. 326 for _, clc := range clcMap[sdkVer] { 327 if clc.Name != lib { 328 // Ok, a different library. 329 } else if clc.Host == hostPath && clc.Device == devicePath { 330 // Ok, the same library with the same paths. Don't re-add it, but don't raise an error 331 // either, as the same library may be reachable via different transitional dependencies. 332 return nil 333 } else { 334 // Fail, as someone is trying to add the same library with different paths. This likely 335 // indicates an error somewhere else, like trying to add a stub library. 336 return fmt.Errorf("a <uses-library> named %q is already in class loader context,"+ 337 "but the library paths are different:\t\n", lib) 338 } 339 } 340 341 clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{ 342 Name: lib, 343 Optional: optional, 344 Implicit: implicit, 345 Host: hostPath, 346 Device: devicePath, 347 Subcontexts: subcontexts, 348 }) 349 return nil 350} 351 352// Add class loader context for the given SDK version. Don't fail on unknown build/install paths, as 353// libraries with unknown paths still need to be processed by manifest_fixer (which doesn't care 354// about paths). For the subset of libraries that are used in dexpreopt, their build/install paths 355// are validated later before CLC is used (in validateClassLoaderContext). 356func (clcMap ClassLoaderContextMap) AddContext(ctx android.ModuleInstallPathContext, sdkVer int, 357 lib string, optional, implicit bool, hostPath, installPath android.Path, 358 nestedClcMap ClassLoaderContextMap) { 359 360 err := clcMap.addContext(ctx, sdkVer, lib, optional, implicit, hostPath, installPath, nestedClcMap) 361 if err != nil { 362 ctx.ModuleErrorf(err.Error()) 363 } 364} 365 366// Merge the other class loader context map into this one, do not override existing entries. 367// The implicitRootLib parameter is the name of the library for which the other class loader 368// context map was constructed. If the implicitRootLib is itself a <uses-library>, it should be 369// already present in the class loader context (with the other context as its subcontext) -- in 370// that case do not re-add the other context. Otherwise add the other context at the top-level. 371func (clcMap ClassLoaderContextMap) AddContextMap(otherClcMap ClassLoaderContextMap, implicitRootLib string) { 372 if otherClcMap == nil { 373 return 374 } 375 376 // If the implicit root of the merged map is already present as one of top-level subtrees, do 377 // not merge it second time. 378 for _, clc := range clcMap[AnySdkVersion] { 379 if clc.Name == implicitRootLib { 380 return 381 } 382 } 383 384 for sdkVer, otherClcs := range otherClcMap { 385 for _, otherClc := range otherClcs { 386 alreadyHave := false 387 for _, clc := range clcMap[sdkVer] { 388 if clc.Name == otherClc.Name { 389 alreadyHave = true 390 break 391 } 392 } 393 if !alreadyHave { 394 clcMap[sdkVer] = append(clcMap[sdkVer], otherClc) 395 } 396 } 397 } 398} 399 400// Returns top-level libraries in the CLC (conditional CLC, i.e. compatibility libraries are not 401// included). This is the list of libraries that should be in the <uses-library> tags in the 402// manifest. Some of them may be present in the source manifest, others are added by manifest_fixer. 403// Required and optional libraries are in separate lists. 404func (clcMap ClassLoaderContextMap) usesLibs(implicit bool) (required []string, optional []string) { 405 if clcMap != nil { 406 clcs := clcMap[AnySdkVersion] 407 required = make([]string, 0, len(clcs)) 408 optional = make([]string, 0, len(clcs)) 409 for _, clc := range clcs { 410 if implicit && !clc.Implicit { 411 // Skip, this is an explicit library and we need only the implicit ones. 412 } else if clc.Optional { 413 optional = append(optional, clc.Name) 414 } else { 415 required = append(required, clc.Name) 416 } 417 } 418 } 419 return required, optional 420} 421 422func (clcMap ClassLoaderContextMap) UsesLibs() ([]string, []string) { 423 return clcMap.usesLibs(false) 424} 425 426func (clcMap ClassLoaderContextMap) ImplicitUsesLibs() ([]string, []string) { 427 return clcMap.usesLibs(true) 428} 429 430func (clcMap ClassLoaderContextMap) Dump() string { 431 jsonCLC := toJsonClassLoaderContext(clcMap) 432 bytes, err := json.MarshalIndent(jsonCLC, "", " ") 433 if err != nil { 434 panic(err) 435 } 436 return string(bytes) 437} 438 439// excludeLibsFromCLCList excludes the libraries from the ClassLoaderContext in this list. 440// 441// This treats the supplied list as being immutable (as it may come from a dependency). So, it 442// implements copy-on-exclusion logic. That means that if any of the excluded libraries are used 443// within the contexts in the list then this will return a deep copy of the list without those 444// libraries. 445// 446// If any of the ClassLoaderContext in the list reference the excluded libraries then this returns a 447// copy of this list without the excluded libraries and true to indicate that this was copied. 448// 449// Otherwise, this returns the list and false to indicate that this was not copied. 450func excludeLibsFromCLCList(clcList []*ClassLoaderContext, excludedLibs []string) ([]*ClassLoaderContext, bool) { 451 modifiedList := false 452 copiedList := make([]*ClassLoaderContext, 0, len(clcList)) 453 for _, clc := range clcList { 454 resultClc, modifiedClc := clc.excludeLibs(excludedLibs) 455 if resultClc != nil { 456 copiedList = append(copiedList, resultClc) 457 } 458 modifiedList = modifiedList || modifiedClc 459 } 460 461 if modifiedList { 462 return copiedList, true 463 } else { 464 return clcList, false 465 } 466} 467 468// ExcludeLibs excludes the libraries from the ClassLoaderContextMap. 469// 470// If the list o libraries is empty then this returns the ClassLoaderContextMap. 471// 472// This treats the ClassLoaderContextMap as being immutable (as it may come from a dependency). So, 473// it implements copy-on-exclusion logic. That means that if any of the excluded libraries are used 474// within the contexts in the map then this will return a deep copy of the map without those 475// libraries. 476// 477// Otherwise, this returns the map unchanged. 478func (clcMap ClassLoaderContextMap) ExcludeLibs(excludedLibs []string) ClassLoaderContextMap { 479 if len(excludedLibs) == 0 { 480 return clcMap 481 } 482 483 excludedClcMap := make(ClassLoaderContextMap) 484 modifiedMap := false 485 for sdkVersion, clcList := range clcMap { 486 excludedList, modifiedList := excludeLibsFromCLCList(clcList, excludedLibs) 487 if len(excludedList) != 0 { 488 excludedClcMap[sdkVersion] = excludedList 489 } 490 modifiedMap = modifiedMap || modifiedList 491 } 492 493 if modifiedMap { 494 return excludedClcMap 495 } else { 496 return clcMap 497 } 498} 499 500// Now that the full unconditional context is known, reconstruct conditional context. 501// Apply filters for individual libraries, mirroring what the PackageManager does when it 502// constructs class loader context on device. 503// 504// TODO(b/132357300): remove "android.hidl.manager" and "android.hidl.base" for non-system apps. 505// 506func fixClassLoaderContext(clcMap ClassLoaderContextMap) { 507 required, optional := clcMap.UsesLibs() 508 usesLibs := append(required, optional...) 509 510 for sdkVer, clcs := range clcMap { 511 if sdkVer == AnySdkVersion { 512 continue 513 } 514 fixedClcs := []*ClassLoaderContext{} 515 for _, clc := range clcs { 516 if android.InList(clc.Name, usesLibs) { 517 // skip compatibility libraries that are already included in unconditional context 518 } else if clc.Name == AndroidTestMock && !android.InList("android.test.runner", usesLibs) { 519 // android.test.mock is only needed as a compatibility library (in conditional class 520 // loader context) if android.test.runner is used, otherwise skip it 521 } else { 522 fixedClcs = append(fixedClcs, clc) 523 } 524 clcMap[sdkVer] = fixedClcs 525 } 526 } 527} 528 529// Return true if all build/install library paths are valid (including recursive subcontexts), 530// otherwise return false. A build path is valid if it's not nil. An install path is valid if it's 531// not equal to a special "error" value. 532func validateClassLoaderContext(clcMap ClassLoaderContextMap) (bool, error) { 533 for sdkVer, clcs := range clcMap { 534 if valid, err := validateClassLoaderContextRec(sdkVer, clcs); !valid || err != nil { 535 return valid, err 536 } 537 } 538 return true, nil 539} 540 541// Helper function for validateClassLoaderContext() that handles recursion. 542func validateClassLoaderContextRec(sdkVer int, clcs []*ClassLoaderContext) (bool, error) { 543 for _, clc := range clcs { 544 if clc.Host == nil || clc.Device == UnknownInstallLibraryPath { 545 if sdkVer == AnySdkVersion { 546 // Return error if dexpreopt doesn't know paths to one of the <uses-library> 547 // dependencies. In the future we may need to relax this and just disable dexpreopt. 548 if clc.Host == nil { 549 return false, fmt.Errorf("invalid build path for <uses-library> \"%s\"", clc.Name) 550 } else { 551 return false, fmt.Errorf("invalid install path for <uses-library> \"%s\"", clc.Name) 552 } 553 } else { 554 // No error for compatibility libraries, as Soong doesn't know if they are needed 555 // (this depends on the targetSdkVersion in the manifest), but the CLC is invalid. 556 return false, nil 557 } 558 } 559 if valid, err := validateClassLoaderContextRec(sdkVer, clc.Subcontexts); !valid || err != nil { 560 return valid, err 561 } 562 } 563 return true, nil 564} 565 566// Return the class loader context as a string, and a slice of build paths for all dependencies. 567// Perform a depth-first preorder traversal of the class loader context tree for each SDK version. 568// Return the resulting string and a slice of on-host build paths to all library dependencies. 569func ComputeClassLoaderContext(clcMap ClassLoaderContextMap) (clcStr string, paths android.Paths) { 570 // CLC for different SDK versions should come in specific order that agrees with PackageManager. 571 // Since PackageManager processes SDK versions in ascending order and prepends compatibility 572 // libraries at the front, the required order is descending, except for AnySdkVersion that has 573 // numerically the largest order, but must be the last one. Example of correct order: [30, 29, 574 // 28, AnySdkVersion]. There are Soong tests to ensure that someone doesn't change this by 575 // accident, but there is no way to guard against changes in the PackageManager, except for 576 // grepping logcat on the first boot for absence of the following messages: 577 // 578 // `logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch` 579 // 580 versions := make([]int, 0, len(clcMap)) 581 for ver, _ := range clcMap { 582 if ver != AnySdkVersion { 583 versions = append(versions, ver) 584 } 585 } 586 sort.Sort(sort.Reverse(sort.IntSlice(versions))) // descending order 587 versions = append(versions, AnySdkVersion) 588 589 for _, sdkVer := range versions { 590 sdkVerStr := fmt.Sprintf("%d", sdkVer) 591 if sdkVer == AnySdkVersion { 592 sdkVerStr = "any" // a special keyword that means any SDK version 593 } 594 hostClc, targetClc, hostPaths := computeClassLoaderContextRec(clcMap[sdkVer]) 595 if hostPaths != nil { 596 clcStr += fmt.Sprintf(" --host-context-for-sdk %s %s", sdkVerStr, hostClc) 597 clcStr += fmt.Sprintf(" --target-context-for-sdk %s %s", sdkVerStr, targetClc) 598 } 599 paths = append(paths, hostPaths...) 600 } 601 return clcStr, android.FirstUniquePaths(paths) 602} 603 604// Helper function for ComputeClassLoaderContext() that handles recursion. 605func computeClassLoaderContextRec(clcs []*ClassLoaderContext) (string, string, android.Paths) { 606 var paths android.Paths 607 var clcsHost, clcsTarget []string 608 609 for _, clc := range clcs { 610 subClcHost, subClcTarget, subPaths := computeClassLoaderContextRec(clc.Subcontexts) 611 if subPaths != nil { 612 subClcHost = "{" + subClcHost + "}" 613 subClcTarget = "{" + subClcTarget + "}" 614 } 615 616 clcsHost = append(clcsHost, "PCL["+clc.Host.String()+"]"+subClcHost) 617 clcsTarget = append(clcsTarget, "PCL["+clc.Device+"]"+subClcTarget) 618 619 paths = append(paths, clc.Host) 620 paths = append(paths, subPaths...) 621 } 622 623 clcHost := strings.Join(clcsHost, "#") 624 clcTarget := strings.Join(clcsTarget, "#") 625 626 return clcHost, clcTarget, paths 627} 628 629// Class loader contexts that come from Make via JSON dexpreopt.config. JSON CLC representation is 630// the same as Soong representation except that SDK versions and paths are represented with strings. 631type jsonClassLoaderContext struct { 632 Name string 633 Optional bool 634 Implicit bool 635 Host string 636 Device string 637 Subcontexts []*jsonClassLoaderContext 638} 639 640// A map from SDK version (represented with a JSON string) to JSON CLCs. 641type jsonClassLoaderContextMap map[string][]*jsonClassLoaderContext 642 643// Convert JSON CLC map to Soong represenation. 644func fromJsonClassLoaderContext(ctx android.PathContext, jClcMap jsonClassLoaderContextMap) ClassLoaderContextMap { 645 clcMap := make(ClassLoaderContextMap) 646 for sdkVerStr, clcs := range jClcMap { 647 sdkVer, ok := strconv.Atoi(sdkVerStr) 648 if ok != nil { 649 if sdkVerStr == "any" { 650 sdkVer = AnySdkVersion 651 } else { 652 android.ReportPathErrorf(ctx, "failed to parse SDK version in dexpreopt.config: '%s'", sdkVerStr) 653 } 654 } 655 clcMap[sdkVer] = fromJsonClassLoaderContextRec(ctx, clcs) 656 } 657 return clcMap 658} 659 660// Recursive helper for fromJsonClassLoaderContext. 661func fromJsonClassLoaderContextRec(ctx android.PathContext, jClcs []*jsonClassLoaderContext) []*ClassLoaderContext { 662 clcs := make([]*ClassLoaderContext, 0, len(jClcs)) 663 for _, clc := range jClcs { 664 clcs = append(clcs, &ClassLoaderContext{ 665 Name: clc.Name, 666 Optional: clc.Optional, 667 Implicit: clc.Implicit, 668 Host: constructPath(ctx, clc.Host), 669 Device: clc.Device, 670 Subcontexts: fromJsonClassLoaderContextRec(ctx, clc.Subcontexts), 671 }) 672 } 673 return clcs 674} 675 676// Convert Soong CLC map to JSON representation for Make. 677func toJsonClassLoaderContext(clcMap ClassLoaderContextMap) jsonClassLoaderContextMap { 678 jClcMap := make(jsonClassLoaderContextMap) 679 for sdkVer, clcs := range clcMap { 680 sdkVerStr := fmt.Sprintf("%d", sdkVer) 681 jClcMap[sdkVerStr] = toJsonClassLoaderContextRec(clcs) 682 } 683 return jClcMap 684} 685 686// Recursive helper for toJsonClassLoaderContext. 687func toJsonClassLoaderContextRec(clcs []*ClassLoaderContext) []*jsonClassLoaderContext { 688 jClcs := make([]*jsonClassLoaderContext, len(clcs)) 689 for i, clc := range clcs { 690 var host string 691 if clc.Host == nil { 692 // Defer build failure to when this CLC is actually used. 693 host = fmt.Sprintf("implementation-jar-for-%s-is-not-available.jar", clc.Name) 694 } else { 695 host = clc.Host.String() 696 } 697 jClcs[i] = &jsonClassLoaderContext{ 698 Name: clc.Name, 699 Optional: clc.Optional, 700 Implicit: clc.Implicit, 701 Host: host, 702 Device: clc.Device, 703 Subcontexts: toJsonClassLoaderContextRec(clc.Subcontexts), 704 } 705 } 706 return jClcs 707} 708