• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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//
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			clcStr, _ := ComputeClassLoaderContext(nestedClcMap)
314			return fmt.Errorf("nested class loader context shouldn't have conditional part: %s", clcStr)
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			return nil
327		} else {
328			// Fail, as someone is trying to add the same library with different paths. This likely
329			// indicates an error somewhere else, like trying to add a stub library.
330			return fmt.Errorf("a <uses-library> named %q is already in class loader context,"+
331				"but the library paths are different:\t\n", lib)
332		}
333	}
334
335	clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{
336		Name:        lib,
337		Optional:    optional,
338		Host:        hostPath,
339		Device:      devicePath,
340		Subcontexts: subcontexts,
341	})
342	return nil
343}
344
345// Add class loader context for the given SDK version. Don't fail on unknown build/install paths, as
346// libraries with unknown paths still need to be processed by manifest_fixer (which doesn't care
347// about paths). For the subset of libraries that are used in dexpreopt, their build/install paths
348// are validated later before CLC is used (in validateClassLoaderContext).
349func (clcMap ClassLoaderContextMap) AddContext(ctx android.ModuleInstallPathContext, sdkVer int,
350	lib string, optional bool, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) {
351
352	err := clcMap.addContext(ctx, sdkVer, lib, optional, hostPath, installPath, nestedClcMap)
353	if err != nil {
354		ctx.ModuleErrorf(err.Error())
355	}
356}
357
358// Merge the other class loader context map into this one, do not override existing entries.
359// The implicitRootLib parameter is the name of the library for which the other class loader
360// context map was constructed. If the implicitRootLib is itself a <uses-library>, it should be
361// already present in the class loader context (with the other context as its subcontext) -- in
362// that case do not re-add the other context. Otherwise add the other context at the top-level.
363func (clcMap ClassLoaderContextMap) AddContextMap(otherClcMap ClassLoaderContextMap, implicitRootLib string) {
364	if otherClcMap == nil {
365		return
366	}
367
368	// If the implicit root of the merged map is already present as one of top-level subtrees, do
369	// not merge it second time.
370	for _, clc := range clcMap[AnySdkVersion] {
371		if clc.Name == implicitRootLib {
372			return
373		}
374	}
375
376	for sdkVer, otherClcs := range otherClcMap {
377		for _, otherClc := range otherClcs {
378			alreadyHave := false
379			for _, clc := range clcMap[sdkVer] {
380				if clc.Name == otherClc.Name {
381					alreadyHave = true
382					break
383				}
384			}
385			if !alreadyHave {
386				clcMap[sdkVer] = append(clcMap[sdkVer], otherClc)
387			}
388		}
389	}
390}
391
392// Returns top-level libraries in the CLC (conditional CLC, i.e. compatibility libraries are not
393// included). This is the list of libraries that should be in the <uses-library> tags in the
394// manifest. Some of them may be present in the source manifest, others are added by manifest_fixer.
395// Required and optional libraries are in separate lists.
396func (clcMap ClassLoaderContextMap) UsesLibs() (required []string, optional []string) {
397	if clcMap != nil {
398		clcs := clcMap[AnySdkVersion]
399		required = make([]string, 0, len(clcs))
400		optional = make([]string, 0, len(clcs))
401		for _, clc := range clcs {
402			if clc.Optional {
403				optional = append(optional, clc.Name)
404			} else {
405				required = append(required, clc.Name)
406			}
407		}
408	}
409	return required, optional
410}
411
412func (clcMap ClassLoaderContextMap) Dump() string {
413	jsonCLC := toJsonClassLoaderContext(clcMap)
414	bytes, err := json.MarshalIndent(jsonCLC, "", "  ")
415	if err != nil {
416		panic(err)
417	}
418	return string(bytes)
419}
420
421// excludeLibsFromCLCList excludes the libraries from the ClassLoaderContext in this list.
422//
423// This treats the supplied list as being immutable (as it may come from a dependency). So, it
424// implements copy-on-exclusion logic. That means that if any of the excluded libraries are used
425// within the contexts in the list then this will return a deep copy of the list without those
426// libraries.
427//
428// If any of the ClassLoaderContext in the list reference the excluded libraries then this returns a
429// copy of this list without the excluded libraries and true to indicate that this was copied.
430//
431// Otherwise, this returns the list and false to indicate that this was not copied.
432func excludeLibsFromCLCList(clcList []*ClassLoaderContext, excludedLibs []string) ([]*ClassLoaderContext, bool) {
433	modifiedList := false
434	copiedList := make([]*ClassLoaderContext, 0, len(clcList))
435	for _, clc := range clcList {
436		resultClc, modifiedClc := clc.excludeLibs(excludedLibs)
437		if resultClc != nil {
438			copiedList = append(copiedList, resultClc)
439		}
440		modifiedList = modifiedList || modifiedClc
441	}
442
443	if modifiedList {
444		return copiedList, true
445	} else {
446		return clcList, false
447	}
448}
449
450// ExcludeLibs excludes the libraries from the ClassLoaderContextMap.
451//
452// If the list o libraries is empty then this returns the ClassLoaderContextMap.
453//
454// This treats the ClassLoaderContextMap as being immutable (as it may come from a dependency). So,
455// it implements copy-on-exclusion logic. That means that if any of the excluded libraries are used
456// within the contexts in the map then this will return a deep copy of the map without those
457// libraries.
458//
459// Otherwise, this returns the map unchanged.
460func (clcMap ClassLoaderContextMap) ExcludeLibs(excludedLibs []string) ClassLoaderContextMap {
461	if len(excludedLibs) == 0 {
462		return clcMap
463	}
464
465	excludedClcMap := make(ClassLoaderContextMap)
466	modifiedMap := false
467	for sdkVersion, clcList := range clcMap {
468		excludedList, modifiedList := excludeLibsFromCLCList(clcList, excludedLibs)
469		if len(excludedList) != 0 {
470			excludedClcMap[sdkVersion] = excludedList
471		}
472		modifiedMap = modifiedMap || modifiedList
473	}
474
475	if modifiedMap {
476		return excludedClcMap
477	} else {
478		return clcMap
479	}
480}
481
482// Now that the full unconditional context is known, reconstruct conditional context.
483// Apply filters for individual libraries, mirroring what the PackageManager does when it
484// constructs class loader context on device.
485//
486// TODO(b/132357300): remove "android.hidl.manager" and "android.hidl.base" for non-system apps.
487func fixClassLoaderContext(clcMap ClassLoaderContextMap) {
488	required, optional := clcMap.UsesLibs()
489	usesLibs := append(required, optional...)
490
491	for sdkVer, clcs := range clcMap {
492		if sdkVer == AnySdkVersion {
493			continue
494		}
495		fixedClcs := []*ClassLoaderContext{}
496		for _, clc := range clcs {
497			if android.InList(clc.Name, usesLibs) {
498				// skip compatibility libraries that are already included in unconditional context
499			} else if clc.Name == AndroidTestMock && !android.InList("android.test.runner", usesLibs) {
500				// android.test.mock is only needed as a compatibility library (in conditional class
501				// loader context) if android.test.runner is used, otherwise skip it
502			} else {
503				fixedClcs = append(fixedClcs, clc)
504			}
505			clcMap[sdkVer] = fixedClcs
506		}
507	}
508}
509
510// Return true if all build/install library paths are valid (including recursive subcontexts),
511// otherwise return false. A build path is valid if it's not nil. An install path is valid if it's
512// not equal to a special "error" value.
513func validateClassLoaderContext(clcMap ClassLoaderContextMap) (bool, error) {
514	for sdkVer, clcs := range clcMap {
515		if valid, err := validateClassLoaderContextRec(sdkVer, clcs); !valid || err != nil {
516			return valid, err
517		}
518	}
519	return true, nil
520}
521
522// Helper function for validateClassLoaderContext() that handles recursion.
523func validateClassLoaderContextRec(sdkVer int, clcs []*ClassLoaderContext) (bool, error) {
524	for _, clc := range clcs {
525		if clc.Host == nil || clc.Device == UnknownInstallLibraryPath {
526			if sdkVer == AnySdkVersion {
527				// Return error if dexpreopt doesn't know paths to one of the <uses-library>
528				// dependencies. In the future we may need to relax this and just disable dexpreopt.
529				if clc.Host == nil {
530					return false, fmt.Errorf("invalid build path for <uses-library> \"%s\"", clc.Name)
531				} else {
532					return false, fmt.Errorf("invalid install path for <uses-library> \"%s\"", clc.Name)
533				}
534			} else {
535				// No error for compatibility libraries, as Soong doesn't know if they are needed
536				// (this depends on the targetSdkVersion in the manifest), but the CLC is invalid.
537				return false, nil
538			}
539		}
540		if valid, err := validateClassLoaderContextRec(sdkVer, clc.Subcontexts); !valid || err != nil {
541			return valid, err
542		}
543	}
544	return true, nil
545}
546
547// Return the class loader context as a string, and a slice of build paths for all dependencies.
548// Perform a depth-first preorder traversal of the class loader context tree for each SDK version.
549// Return the resulting string and a slice of on-host build paths to all library dependencies.
550func ComputeClassLoaderContext(clcMap ClassLoaderContextMap) (clcStr string, paths android.Paths) {
551	// CLC for different SDK versions should come in specific order that agrees with PackageManager.
552	// Since PackageManager processes SDK versions in ascending order and prepends compatibility
553	// libraries at the front, the required order is descending, except for AnySdkVersion that has
554	// numerically the largest order, but must be the last one. Example of correct order: [30, 29,
555	// 28, AnySdkVersion]. There are Soong tests to ensure that someone doesn't change this by
556	// accident, but there is no way to guard against changes in the PackageManager, except for
557	// grepping logcat on the first boot for absence of the following messages:
558	//
559	//   `logcat | grep -E 'ClassLoaderContext [a-z ]+ mismatch`
560	//
561	versions := make([]int, 0, len(clcMap))
562	for ver, _ := range clcMap {
563		if ver != AnySdkVersion {
564			versions = append(versions, ver)
565		}
566	}
567	sort.Sort(sort.Reverse(sort.IntSlice(versions))) // descending order
568	versions = append(versions, AnySdkVersion)
569
570	for _, sdkVer := range versions {
571		sdkVerStr := fmt.Sprintf("%d", sdkVer)
572		if sdkVer == AnySdkVersion {
573			sdkVerStr = "any" // a special keyword that means any SDK version
574		}
575		hostClc, targetClc, hostPaths := computeClassLoaderContextRec(clcMap[sdkVer])
576		if hostPaths != nil {
577			clcStr += fmt.Sprintf(" --host-context-for-sdk %s %s", sdkVerStr, hostClc)
578			clcStr += fmt.Sprintf(" --target-context-for-sdk %s %s", sdkVerStr, targetClc)
579		}
580		paths = append(paths, hostPaths...)
581	}
582	return clcStr, android.FirstUniquePaths(paths)
583}
584
585// Helper function for ComputeClassLoaderContext() that handles recursion.
586func computeClassLoaderContextRec(clcs []*ClassLoaderContext) (string, string, android.Paths) {
587	var paths android.Paths
588	var clcsHost, clcsTarget []string
589
590	for _, clc := range clcs {
591		subClcHost, subClcTarget, subPaths := computeClassLoaderContextRec(clc.Subcontexts)
592		if subPaths != nil {
593			subClcHost = "{" + subClcHost + "}"
594			subClcTarget = "{" + subClcTarget + "}"
595		}
596
597		clcsHost = append(clcsHost, "PCL["+clc.Host.String()+"]"+subClcHost)
598		clcsTarget = append(clcsTarget, "PCL["+clc.Device+"]"+subClcTarget)
599
600		paths = append(paths, clc.Host)
601		paths = append(paths, subPaths...)
602	}
603
604	clcHost := strings.Join(clcsHost, "#")
605	clcTarget := strings.Join(clcsTarget, "#")
606
607	return clcHost, clcTarget, paths
608}
609
610// Class loader contexts that come from Make via JSON dexpreopt.config. JSON CLC representation is
611// the same as Soong representation except that SDK versions and paths are represented with strings.
612type jsonClassLoaderContext struct {
613	Name        string
614	Optional    bool
615	Host        string
616	Device      string
617	Subcontexts []*jsonClassLoaderContext
618}
619
620// A map from SDK version (represented with a JSON string) to JSON CLCs.
621type jsonClassLoaderContextMap map[string][]*jsonClassLoaderContext
622
623// Convert JSON CLC map to Soong represenation.
624func fromJsonClassLoaderContext(ctx android.PathContext, jClcMap jsonClassLoaderContextMap) ClassLoaderContextMap {
625	clcMap := make(ClassLoaderContextMap)
626	for sdkVerStr, clcs := range jClcMap {
627		sdkVer, ok := strconv.Atoi(sdkVerStr)
628		if ok != nil {
629			if sdkVerStr == "any" {
630				sdkVer = AnySdkVersion
631			} else {
632				android.ReportPathErrorf(ctx, "failed to parse SDK version in dexpreopt.config: '%s'", sdkVerStr)
633			}
634		}
635		clcMap[sdkVer] = fromJsonClassLoaderContextRec(ctx, clcs)
636	}
637	return clcMap
638}
639
640// Recursive helper for fromJsonClassLoaderContext.
641func fromJsonClassLoaderContextRec(ctx android.PathContext, jClcs []*jsonClassLoaderContext) []*ClassLoaderContext {
642	clcs := make([]*ClassLoaderContext, 0, len(jClcs))
643	for _, clc := range jClcs {
644		clcs = append(clcs, &ClassLoaderContext{
645			Name:        clc.Name,
646			Optional:    clc.Optional,
647			Host:        constructPath(ctx, clc.Host),
648			Device:      clc.Device,
649			Subcontexts: fromJsonClassLoaderContextRec(ctx, clc.Subcontexts),
650		})
651	}
652	return clcs
653}
654
655// Convert Soong CLC map to JSON representation for Make.
656func toJsonClassLoaderContext(clcMap ClassLoaderContextMap) jsonClassLoaderContextMap {
657	jClcMap := make(jsonClassLoaderContextMap)
658	for sdkVer, clcs := range clcMap {
659		sdkVerStr := fmt.Sprintf("%d", sdkVer)
660		if sdkVer == AnySdkVersion {
661			sdkVerStr = "any"
662		}
663		jClcMap[sdkVerStr] = toJsonClassLoaderContextRec(clcs)
664	}
665	return jClcMap
666}
667
668// Recursive helper for toJsonClassLoaderContext.
669func toJsonClassLoaderContextRec(clcs []*ClassLoaderContext) []*jsonClassLoaderContext {
670	jClcs := make([]*jsonClassLoaderContext, len(clcs))
671	for i, clc := range clcs {
672		var host string
673		if clc.Host == nil {
674			// Defer build failure to when this CLC is actually used.
675			host = fmt.Sprintf("implementation-jar-for-%s-is-not-available.jar", clc.Name)
676		} else {
677			host = clc.Host.String()
678		}
679		jClcs[i] = &jsonClassLoaderContext{
680			Name:        clc.Name,
681			Optional:    clc.Optional,
682			Host:        host,
683			Device:      clc.Device,
684			Subcontexts: toJsonClassLoaderContextRec(clc.Subcontexts),
685		}
686	}
687	return jClcs
688}
689