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