blob: 77d6ee9e30898e8ac93982942f9581bb15ea0408 [file] [log] [blame]
Ulya Trafimovicheb268862020-10-20 15:16:38 +01001// 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"
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +000019 "strconv"
Ulya Trafimovicheb268862020-10-20 15:16:38 +010020 "strings"
21
22 "android/soong/android"
23)
24
25// These libs are added as <uses-library> dependencies for apps if the targetSdkVersion in the
26// app manifest is less than the specified version. This is needed because these libraries haven't
27// existed prior to certain SDK version, but classes in them were in bootclasspath jars, etc.
28// Some of the compatibility libraries are optional (their <uses-library> tag has "required=false"),
29// so that if this library is missing this in not a build or run-time error.
30var OrgApacheHttpLegacy = "org.apache.http.legacy"
31var AndroidTestBase = "android.test.base"
32var AndroidTestMock = "android.test.mock"
33var AndroidHidlBase = "android.hidl.base-V1.0-java"
34var AndroidHidlManager = "android.hidl.manager-V1.0-java"
35
36var OptionalCompatUsesLibs28 = []string{
37 OrgApacheHttpLegacy,
38}
39var OptionalCompatUsesLibs30 = []string{
40 AndroidTestBase,
41 AndroidTestMock,
42}
43var CompatUsesLibs29 = []string{
44 AndroidHidlBase,
45 AndroidHidlManager,
46}
47var OptionalCompatUsesLibs = append(android.CopyOf(OptionalCompatUsesLibs28), OptionalCompatUsesLibs30...)
48var CompatUsesLibs = android.CopyOf(CompatUsesLibs29)
49
50const UnknownInstallLibraryPath = "error"
51
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +000052// AnySdkVersion means that the class loader context is needed regardless of the targetSdkVersion
53// of the app. The numeric value affects the key order in the map and, as a result, the order of
54// arguments passed to construct_context.py (high value means that the unconditional context goes
55// last). We use the converntional "current" SDK level (10000), but any big number would do as well.
56const AnySdkVersion int = android.FutureApiLevelInt
Ulya Trafimovicheb268862020-10-20 15:16:38 +010057
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +000058// ClassLoaderContext is a tree of libraries used by the dexpreopted module with their dependencies.
59// The context is used by dex2oat to compile the module and recorded in the AOT-compiled files, so
60// that it can be checked agains the run-time class loader context on device. If there is a mismatch
61// at runtime, AOT-compiled code is rejected.
62type ClassLoaderContext struct {
63 // The name of the library (same as the name of the module that contains it).
64 Name string
65
66 // On-host build path to the library dex file (used in dex2oat argument --class-loader-context).
67 Host android.Path
68
69 // On-device install path (used in dex2oat argument --stored-class-loader-context).
Ulya Trafimovicheb268862020-10-20 15:16:38 +010070 Device string
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +000071
72 // Nested class loader subcontexts for dependencies.
73 Subcontexts []*ClassLoaderContext
Ulya Trafimovicheb268862020-10-20 15:16:38 +010074}
75
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +000076// ClassLoaderContextMap is a map from SDK version to a class loader context.
77// There is a special entry with key AnySdkVersion that stores unconditional class loader context.
78// Other entries store conditional contexts that should be added for some apps that have
79// targetSdkVersion in the manifest lower than the key SDK version.
80type ClassLoaderContextMap map[int][]*ClassLoaderContext
Ulya Trafimovicheb268862020-10-20 15:16:38 +010081
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +000082// Add class loader context for the given library to the map entry for the given SDK version.
83func (clcMap ClassLoaderContextMap) addContext(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
84 hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) error {
Ulya Trafimovicheb268862020-10-20 15:16:38 +010085
86 // If missing dependencies are allowed, the build shouldn't fail when a <uses-library> is
87 // not found. However, this is likely to result is disabling dexpreopt, as it won't be
88 // possible to construct class loader context without on-host and on-device library paths.
89 strict = strict && !ctx.Config().AllowMissingDependencies()
90
91 if hostPath == nil && strict {
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +000092 return fmt.Errorf("unknown build path to <uses-library> \"%s\"", lib)
Ulya Trafimovicheb268862020-10-20 15:16:38 +010093 }
94
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +000095 devicePath := UnknownInstallLibraryPath
Ulya Trafimovicheb268862020-10-20 15:16:38 +010096 if installPath == nil {
97 if android.InList(lib, CompatUsesLibs) || android.InList(lib, OptionalCompatUsesLibs) {
98 // Assume that compatibility libraries are installed in /system/framework.
99 installPath = android.PathForModuleInstall(ctx, "framework", lib+".jar")
100 } else if strict {
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000101 return fmt.Errorf("unknown install path to <uses-library> \"%s\"", lib)
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100102 } else {
103 // For some stub libraries the only known thing is the name of their implementation
104 // library, but the library itself is unavailable (missing or part of a prebuilt). In
105 // such cases we still need to add the library to <uses-library> tags in the manifest,
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000106 // but we cannot use it for dexpreopt.
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100107 }
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100108 }
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000109 if installPath != nil {
110 devicePath = android.InstallPathToOnDevicePath(ctx, installPath.(android.InstallPath))
111 }
112
Ulya Trafimovich5e13a732020-11-03 15:33:03 +0000113 // Nested class loader context shouldn't have conditional part (it is allowed only at the top level).
114 for ver, _ := range nestedClcMap {
115 if ver != AnySdkVersion {
116 clcStr, _ := ComputeClassLoaderContext(nestedClcMap)
117 return fmt.Errorf("nested class loader context shouldn't have conditional part: %s", clcStr)
118 }
119 }
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000120 subcontexts := nestedClcMap[AnySdkVersion]
121
122 // If the library with this name is already present as one of the unconditional top-level
123 // components, do not re-add it.
124 for _, clc := range clcMap[sdkVer] {
125 if clc.Name == lib {
126 return nil
127 }
128 }
129
130 clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{
131 Name: lib,
132 Host: hostPath,
133 Device: devicePath,
134 Subcontexts: subcontexts,
135 })
Ulya Trafimovich69612672020-10-20 17:41:54 +0100136 return nil
137}
138
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000139// Wrapper around addContext that reports errors.
140func (clcMap ClassLoaderContextMap) addContextOrReportError(ctx android.ModuleInstallPathContext, sdkVer int, lib string,
141 hostPath, installPath android.Path, strict bool, nestedClcMap ClassLoaderContextMap) {
Ulya Trafimovich69612672020-10-20 17:41:54 +0100142
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000143 err := clcMap.addContext(ctx, sdkVer, lib, hostPath, installPath, strict, nestedClcMap)
Ulya Trafimovich69612672020-10-20 17:41:54 +0100144 if err != nil {
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000145 ctx.ModuleErrorf(err.Error())
Ulya Trafimovich69612672020-10-20 17:41:54 +0100146 android.ReportPathErrorf(ctx, err.Error())
147 }
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100148}
149
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000150// Add class loader context. Fail on unknown build/install paths.
151func (clcMap ClassLoaderContextMap) AddContext(ctx android.ModuleInstallPathContext, lib string,
152 hostPath, installPath android.Path) {
153
154 clcMap.addContextOrReportError(ctx, AnySdkVersion, lib, hostPath, installPath, true, nil)
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100155}
156
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000157// Add class loader context if the library exists. Don't fail on unknown build/install paths.
158func (clcMap ClassLoaderContextMap) MaybeAddContext(ctx android.ModuleInstallPathContext, lib *string,
159 hostPath, installPath android.Path) {
160
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100161 if lib != nil {
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000162 clcMap.addContextOrReportError(ctx, AnySdkVersion, *lib, hostPath, installPath, false, nil)
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100163 }
164}
165
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000166// Add class loader context for the given SDK version. Fail on unknown build/install paths.
167func (clcMap ClassLoaderContextMap) AddContextForSdk(ctx android.ModuleInstallPathContext, sdkVer int,
168 lib string, hostPath, installPath android.Path, nestedClcMap ClassLoaderContextMap) {
169
170 clcMap.addContextOrReportError(ctx, sdkVer, lib, hostPath, installPath, true, nestedClcMap)
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100171}
172
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000173// Merge the other class loader context map into this one, do not override existing entries.
Ulya Trafimovich18554242020-11-03 15:55:11 +0000174// The implicitRootLib parameter is the name of the library for which the other class loader
175// context map was constructed. If the implicitRootLib is itself a <uses-library>, it should be
176// already present in the class loader context (with the other context as its subcontext) -- in
177// that case do not re-add the other context. Otherwise add the other context at the top-level.
178func (clcMap ClassLoaderContextMap) AddContextMap(otherClcMap ClassLoaderContextMap, implicitRootLib string) {
179 if otherClcMap == nil {
180 return
181 }
182
183 // If the implicit root of the merged map is already present as one of top-level subtrees, do
184 // not merge it second time.
185 for _, clc := range clcMap[AnySdkVersion] {
186 if clc.Name == implicitRootLib {
187 return
188 }
189 }
190
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000191 for sdkVer, otherClcs := range otherClcMap {
192 for _, otherClc := range otherClcs {
193 alreadyHave := false
194 for _, clc := range clcMap[sdkVer] {
195 if clc.Name == otherClc.Name {
196 alreadyHave = true
197 break
198 }
199 }
200 if !alreadyHave {
201 clcMap[sdkVer] = append(clcMap[sdkVer], otherClc)
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100202 }
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100203 }
204 }
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100205}
206
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000207// List of libraries in the unconditional class loader context, excluding dependencies of shared libraries.
208func (clcMap ClassLoaderContextMap) UsesLibs() (ulibs []string) {
209 if clcMap != nil {
210 // compatibility libraries (those in conditional context) are not added to <uses-library> tags
211 ulibs = usesLibsRec(clcMap[AnySdkVersion])
212 ulibs = android.FirstUniqueStrings(ulibs)
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100213 }
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000214 return ulibs
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100215}
216
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000217func usesLibsRec(clcs []*ClassLoaderContext) (ulibs []string) {
218 for _, clc := range clcs {
219 ulibs = append(ulibs, clc.Name)
220 ulibs = append(ulibs, usesLibsRec(clc.Subcontexts)...)
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100221 }
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000222 return ulibs
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100223}
224
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100225// Now that the full unconditional context is known, reconstruct conditional context.
226// Apply filters for individual libraries, mirroring what the PackageManager does when it
227// constructs class loader context on device.
228//
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000229// TODO(b/132357300): remove "android.hidl.manager" and "android.hidl.base" for non-system apps.
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100230//
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000231func fixClassLoaderContext(clcMap ClassLoaderContextMap) {
232 usesLibs := clcMap.UsesLibs()
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100233
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000234 for sdkVer, clcs := range clcMap {
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100235 if sdkVer == AnySdkVersion {
236 continue
237 }
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000238 fixedClcs := []*ClassLoaderContext{}
239 for _, clc := range clcs {
240 if android.InList(clc.Name, usesLibs) {
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100241 // skip compatibility libraries that are already included in unconditional context
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000242 } else if clc.Name == AndroidTestMock && !android.InList("android.test.runner", usesLibs) {
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100243 // android.test.mock is only needed as a compatibility library (in conditional class
244 // loader context) if android.test.runner is used, otherwise skip it
245 } else {
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000246 fixedClcs = append(fixedClcs, clc)
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100247 }
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000248 clcMap[sdkVer] = fixedClcs
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100249 }
250 }
251}
252
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000253// Return true if all build/install library paths are valid (including recursive subcontexts),
254// otherwise return false. A build path is valid if it's not nil. An install path is valid if it's
255// not equal to a special "error" value.
256func validateClassLoaderContext(clcMap ClassLoaderContextMap) (bool, error) {
257 for sdkVer, clcs := range clcMap {
258 if valid, err := validateClassLoaderContextRec(sdkVer, clcs); !valid || err != nil {
259 return valid, err
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100260 }
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000261 }
262 return true, nil
263}
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100264
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000265func validateClassLoaderContextRec(sdkVer int, clcs []*ClassLoaderContext) (bool, error) {
266 for _, clc := range clcs {
267 if clc.Host == nil || clc.Device == UnknownInstallLibraryPath {
268 if sdkVer == AnySdkVersion {
269 // Return error if dexpreopt doesn't know paths to one of the <uses-library>
270 // dependencies. In the future we may need to relax this and just disable dexpreopt.
271 return false, fmt.Errorf("invalid path for <uses-library> \"%s\"", clc.Name)
272 } else {
273 // No error for compatibility libraries, as Soong doesn't know if they are needed
274 // (this depends on the targetSdkVersion in the manifest), but the CLC is invalid.
275 return false, nil
276 }
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100277 }
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000278 if valid, err := validateClassLoaderContextRec(sdkVer, clc.Subcontexts); !valid || err != nil {
279 return valid, err
280 }
281 }
282 return true, nil
283}
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100284
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000285// Return the class loader context as a string, and a slice of build paths for all dependencies.
286// Perform a depth-first preorder traversal of the class loader context tree for each SDK version.
287// Return the resulting string and a slice of on-host build paths to all library dependencies.
288func ComputeClassLoaderContext(clcMap ClassLoaderContextMap) (clcStr string, paths android.Paths) {
289 for _, sdkVer := range android.SortedIntKeys(clcMap) { // determinisitc traversal order
290 sdkVerStr := fmt.Sprintf("%d", sdkVer)
291 if sdkVer == AnySdkVersion {
292 sdkVerStr = "any" // a special keyword that means any SDK version
293 }
294 hostClc, targetClc, hostPaths := computeClassLoaderContextRec(clcMap[sdkVer])
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100295 if hostPaths != nil {
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000296 clcStr += fmt.Sprintf(" --host-context-for-sdk %s %s", sdkVerStr, hostClc)
297 clcStr += fmt.Sprintf(" --target-context-for-sdk %s %s", sdkVerStr, targetClc)
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100298 }
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000299 paths = append(paths, hostPaths...)
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100300 }
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000301 return clcStr, android.FirstUniquePaths(paths)
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100302}
303
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000304func computeClassLoaderContextRec(clcs []*ClassLoaderContext) (string, string, android.Paths) {
305 var paths android.Paths
306 var clcsHost, clcsTarget []string
307
308 for _, clc := range clcs {
309 subClcHost, subClcTarget, subPaths := computeClassLoaderContextRec(clc.Subcontexts)
310 if subPaths != nil {
311 subClcHost = "{" + subClcHost + "}"
312 subClcTarget = "{" + subClcTarget + "}"
313 }
314
315 clcsHost = append(clcsHost, "PCL["+clc.Host.String()+"]"+subClcHost)
316 clcsTarget = append(clcsTarget, "PCL["+clc.Device+"]"+subClcTarget)
317
318 paths = append(paths, clc.Host)
319 paths = append(paths, subPaths...)
320 }
321
322 clcHost := strings.Join(clcsHost, "#")
323 clcTarget := strings.Join(clcsTarget, "#")
324
325 return clcHost, clcTarget, paths
326}
327
328// Paths to a <uses-library> on host and on device.
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100329type jsonLibraryPath struct {
330 Host string
331 Device string
332}
333
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000334// Class loader contexts that come from Make (via JSON dexpreopt.config) files have simpler
335// structure than Soong class loader contexts: they are flat maps from a <uses-library> name to its
336// on-host and on-device paths. There are no nested subcontexts. It is a limitation of the current
337// Make implementation.
338type jsonClassLoaderContext map[string]jsonLibraryPath
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100339
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000340// A map from SDK version (represented with a JSON string) to JSON class loader context.
341type jsonClassLoaderContextMap map[string]jsonClassLoaderContext
342
343// Convert JSON class loader context map to ClassLoaderContextMap.
344func fromJsonClassLoaderContext(ctx android.PathContext, jClcMap jsonClassLoaderContextMap) ClassLoaderContextMap {
345 clcMap := make(ClassLoaderContextMap)
346 for sdkVerStr, clc := range jClcMap {
347 sdkVer, ok := strconv.Atoi(sdkVerStr)
348 if ok != nil {
349 if sdkVerStr == "any" {
350 sdkVer = AnySdkVersion
351 } else {
352 android.ReportPathErrorf(ctx, "failed to parse SDK version in dexpreopt.config: '%s'", sdkVerStr)
353 }
354 }
355 for lib, path := range clc {
356 clcMap[sdkVer] = append(clcMap[sdkVer], &ClassLoaderContext{
357 Name: lib,
358 Host: constructPath(ctx, path.Host),
359 Device: path.Device,
360 Subcontexts: nil,
361 })
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100362 }
363 }
Ulya Trafimovich8cbc5d22020-11-03 15:15:46 +0000364 return clcMap
Ulya Trafimovicheb268862020-10-20 15:16:38 +0100365}