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