blob: 48269e6e1ab948bf9e499f666b4dd528dc703688 [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"
19 "path/filepath"
20 "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
52const AnySdkVersion int = 9999 // should go last in class loader context
53
54// LibraryPath contains paths to the library DEX jar on host and on device.
55type LibraryPath struct {
56 Host android.Path
57 Device string
58}
59
60// LibraryPaths is a map from library name to on-host and on-device paths to its DEX jar.
61type LibraryPaths map[string]*LibraryPath
62
63type classLoaderContext struct {
64 // Library names
65 Names []string
66
67 // The class loader context using paths in the build.
68 Host android.Paths
69
70 // The class loader context using paths as they will be on the device.
71 Target []string
72}
73
74// A map of class loader contexts for each SDK version.
75// A map entry for "any" version contains libraries that are unconditionally added to class loader
76// context. Map entries for existing versions contains libraries that were in the default classpath
77// until that API version, and should be added to class loader context if and only if the
78// targetSdkVersion in the manifest or APK is less than that API version.
79type classLoaderContextMap map[int]*classLoaderContext
80
81// Add a new library path to the map, unless a path for this library already exists.
82// If necessary, check that the build and install paths exist.
83func (libPaths LibraryPaths) addLibraryPath(ctx android.ModuleContext, lib string,
84 hostPath, installPath android.Path, strict bool) {
85
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 {
92 android.ReportPathErrorf(ctx, "unknown build path to <uses-library> '%s'", lib)
93 }
94
95 if installPath == nil {
96 if android.InList(lib, CompatUsesLibs) || android.InList(lib, OptionalCompatUsesLibs) {
97 // Assume that compatibility libraries are installed in /system/framework.
98 installPath = android.PathForModuleInstall(ctx, "framework", lib+".jar")
99 } else if strict {
100 android.ReportPathErrorf(ctx, "unknown install path to <uses-library> '%s'", lib)
101 }
102 }
103
104 // Add a library only if the build and install path to it is known.
105 if _, present := libPaths[lib]; !present {
106 var devicePath string
107 if installPath != nil {
108 devicePath = android.InstallPathToOnDevicePath(ctx, installPath.(android.InstallPath))
109 } else {
110 // For some stub libraries the only known thing is the name of their implementation
111 // library, but the library itself is unavailable (missing or part of a prebuilt). In
112 // such cases we still need to add the library to <uses-library> tags in the manifest,
113 // but we cannot use if for dexpreopt.
114 devicePath = UnknownInstallLibraryPath
115 }
116 libPaths[lib] = &LibraryPath{hostPath, devicePath}
117 }
118}
119
120// Add a new library path to the map. Enforce checks that the library paths exist.
121func (libPaths LibraryPaths) AddLibraryPath(ctx android.ModuleContext, lib string, hostPath, installPath android.Path) {
122 libPaths.addLibraryPath(ctx, lib, hostPath, installPath, true)
123}
124
125// Add a new library path to the map, if the library exists (name is not nil).
126// Don't enforce checks that the library paths exist. Some libraries may be missing from the build,
127// but their names still need to be added to <uses-library> tags in the manifest.
128func (libPaths LibraryPaths) MaybeAddLibraryPath(ctx android.ModuleContext, lib *string, hostPath, installPath android.Path) {
129 if lib != nil {
130 libPaths.addLibraryPath(ctx, *lib, hostPath, installPath, false)
131 }
132}
133
134// Add library paths from the second map to the first map (do not override existing entries).
135func (libPaths LibraryPaths) AddLibraryPaths(otherPaths LibraryPaths) {
136 for lib, path := range otherPaths {
137 if _, present := libPaths[lib]; !present {
138 libPaths[lib] = path
139 }
140 }
141}
142
143func (m classLoaderContextMap) getValue(sdkVer int) *classLoaderContext {
144 if _, ok := m[sdkVer]; !ok {
145 m[sdkVer] = &classLoaderContext{}
146 }
147 return m[sdkVer]
148}
149
150func (clc *classLoaderContext) addLib(lib string, hostPath android.Path, targetPath string) {
151 clc.Names = append(clc.Names, lib)
152 clc.Host = append(clc.Host, hostPath)
153 clc.Target = append(clc.Target, targetPath)
154}
155
156func (m classLoaderContextMap) addLibs(ctx android.PathContext, sdkVer int, module *ModuleConfig, libs ...string) bool {
157 clc := m.getValue(sdkVer)
158 for _, lib := range libs {
159 if p, ok := module.LibraryPaths[lib]; ok && p.Host != nil && p.Device != UnknownInstallLibraryPath {
160 clc.addLib(lib, p.Host, p.Device)
161 } else {
162 if sdkVer == AnySdkVersion {
163 // Fail the build if dexpreopt doesn't know paths to one of the <uses-library>
164 // dependencies. In the future we may need to relax this and just disable dexpreopt.
165 android.ReportPathErrorf(ctx, "dexpreopt cannot find path for <uses-library> '%s'", lib)
166 } else {
167 // No error for compatibility libraries, as Soong doesn't know if they are needed
168 // (this depends on the targetSdkVersion in the manifest).
169 }
170 return false
171 }
172 }
173 return true
174}
175
176func (m classLoaderContextMap) addSystemServerLibs(sdkVer int, ctx android.PathContext, module *ModuleConfig, libs ...string) {
177 clc := m.getValue(sdkVer)
178 for _, lib := range libs {
179 clc.addLib(lib, SystemServerDexJarHostPath(ctx, lib), filepath.Join("/system/framework", lib+".jar"))
180 }
181}
182
183func (m classLoaderContextMap) usesLibs() []string {
184 if clc, ok := m[AnySdkVersion]; ok {
185 return clc.Names
186 }
187 return nil
188}
189
190// genClassLoaderContext generates host and target class loader context to be passed to the dex2oat
191// command for the dexpreopted module. There are three possible cases:
192//
193// 1. System server jars. They have a special class loader context that includes other system
194// server jars.
195//
196// 2. Library jars or APKs which have precise list of their <uses-library> libs. Their class loader
197// context includes build and on-device paths to these libs. In some cases it may happen that
198// the path to a <uses-library> is unknown (e.g. the dexpreopted module may depend on stubs
199// library, whose implementation library is missing from the build altogether). In such case
200// dexpreopting with the <uses-library> is impossible, and dexpreopting without it is pointless,
201// as the runtime classpath won't match and the dexpreopted code will be discarded. Therefore in
202// such cases the function returns nil, which disables dexpreopt.
203//
204// 3. All other library jars or APKs for which the exact <uses-library> list is unknown. They use
205// the unsafe &-classpath workaround that means empty class loader context and absence of runtime
206// check that the class loader context provided by the PackageManager agrees with the stored
207// class loader context recorded in the .odex file.
208//
209func genClassLoaderContext(ctx android.PathContext, global *GlobalConfig, module *ModuleConfig) *classLoaderContextMap {
210 classLoaderContexts := make(classLoaderContextMap)
211 systemServerJars := NonUpdatableSystemServerJars(ctx, global)
212
213 if jarIndex := android.IndexList(module.Name, systemServerJars); jarIndex >= 0 {
214 // System server jars should be dexpreopted together: class loader context of each jar
215 // should include all preceding jars on the system server classpath.
216 classLoaderContexts.addSystemServerLibs(AnySdkVersion, ctx, module, systemServerJars[:jarIndex]...)
217
218 } else if module.EnforceUsesLibraries {
219 // Unconditional class loader context.
220 usesLibs := append(copyOf(module.UsesLibraries), module.OptionalUsesLibraries...)
221 if !classLoaderContexts.addLibs(ctx, AnySdkVersion, module, usesLibs...) {
222 return nil
223 }
224
225 // Conditional class loader context for API version < 28.
226 const httpLegacy = "org.apache.http.legacy"
227 if !classLoaderContexts.addLibs(ctx, 28, module, httpLegacy) {
228 return nil
229 }
230
231 // Conditional class loader context for API version < 29.
232 usesLibs29 := []string{
233 "android.hidl.base-V1.0-java",
234 "android.hidl.manager-V1.0-java",
235 }
236 if !classLoaderContexts.addLibs(ctx, 29, module, usesLibs29...) {
237 return nil
238 }
239
240 // Conditional class loader context for API version < 30.
241 if !classLoaderContexts.addLibs(ctx, 30, module, OptionalCompatUsesLibs30...) {
242 return nil
243 }
244
245 } else {
246 // Pass special class loader context to skip the classpath and collision check.
247 // This will get removed once LOCAL_USES_LIBRARIES is enforced.
248 // Right now LOCAL_USES_LIBRARIES is opt in, for the case where it's not specified we still default
249 // to the &.
250 }
251
252 fixConditionalClassLoaderContext(classLoaderContexts)
253
254 return &classLoaderContexts
255}
256
257// Find build and install paths to "android.hidl.base". The library must be present in conditional
258// class loader context for SDK version 29, because it's one of the compatibility libraries.
259func findHidlBasePaths(ctx android.PathContext, clcMap classLoaderContextMap) (android.Path, string) {
260 var hostPath android.Path
261 targetPath := UnknownInstallLibraryPath
262
263 if clc, ok := clcMap[29]; ok {
264 for i, lib := range clc.Names {
265 if lib == AndroidHidlBase {
266 hostPath = clc.Host[i]
267 targetPath = clc.Target[i]
268 break
269 }
270 }
271 }
272
273 // Fail if the library paths were not found. This may happen if the function is called at the
274 // wrong time (either before the compatibility libraries were added to context, or after they
275 // have been removed for some reason).
276 if hostPath == nil {
277 android.ReportPathErrorf(ctx, "dexpreopt cannot find build path to '%s'", AndroidHidlBase)
278 } else if targetPath == UnknownInstallLibraryPath {
279 android.ReportPathErrorf(ctx, "dexpreopt cannot find install path to '%s'", AndroidHidlBase)
280 }
281
282 return hostPath, targetPath
283}
284
285// Now that the full unconditional context is known, reconstruct conditional context.
286// Apply filters for individual libraries, mirroring what the PackageManager does when it
287// constructs class loader context on device.
288//
289// TODO(b/132357300):
290// - move handling of android.hidl.manager -> android.hidl.base dependency here
291// - remove android.hidl.manager and android.hidl.base unless the app is a system app.
292//
293func fixConditionalClassLoaderContext(clcMap classLoaderContextMap) {
294 usesLibs := clcMap.usesLibs()
295
296 for sdkVer, clc := range clcMap {
297 if sdkVer == AnySdkVersion {
298 continue
299 }
300 clcMap[sdkVer] = &classLoaderContext{}
301 for i, lib := range clc.Names {
302 if android.InList(lib, usesLibs) {
303 // skip compatibility libraries that are already included in unconditional context
304 } else if lib == AndroidTestMock && !android.InList("android.test.runner", usesLibs) {
305 // android.test.mock is only needed as a compatibility library (in conditional class
306 // loader context) if android.test.runner is used, otherwise skip it
307 } else {
308 clcMap[sdkVer].addLib(lib, clc.Host[i], clc.Target[i])
309 }
310 }
311 }
312}
313
314// Return the class loader context as a string and a slice of build paths for all dependencies.
315func computeClassLoaderContext(ctx android.PathContext, clcMap classLoaderContextMap) (clcStr string, paths android.Paths) {
316 hidlBaseHostPath, hidlBaseTargetPath := findHidlBasePaths(ctx, clcMap)
317
318 for _, ver := range android.SortedIntKeys(clcMap) {
319 clc := clcMap.getValue(ver)
320
321 clcLen := len(clc.Names)
322 if clcLen != len(clc.Host) || clcLen != len(clc.Target) {
323 android.ReportPathErrorf(ctx, "ill-formed class loader context")
324 }
325
326 var hostClc, targetClc []string
327 var hostPaths android.Paths
328
329 for i := 0; i < clcLen; i++ {
330 hostStr := "PCL[" + clc.Host[i].String() + "]"
331 targetStr := "PCL[" + clc.Target[i] + "]"
332
333 // Add dependency of android.hidl.manager on android.hidl.base (it is not tracked as
334 // a regular dependency by the build system, so it needs special handling).
335 if clc.Names[i] == AndroidHidlManager {
336 hostStr += "{PCL[" + hidlBaseHostPath.String() + "]}"
337 targetStr += "{PCL[" + hidlBaseTargetPath + "]}"
338 hostPaths = append(hostPaths, hidlBaseHostPath)
339 }
340
341 hostClc = append(hostClc, hostStr)
342 targetClc = append(targetClc, targetStr)
343 hostPaths = append(hostPaths, clc.Host[i])
344 }
345
346 if hostPaths != nil {
347 sdkVerStr := fmt.Sprintf("%d", ver)
348 if ver == AnySdkVersion {
349 sdkVerStr = "any" // a special keyword that means any SDK version
350 }
351 clcStr += fmt.Sprintf(" --host-context-for-sdk %s %s", sdkVerStr, strings.Join(hostClc, "#"))
352 clcStr += fmt.Sprintf(" --target-context-for-sdk %s %s", sdkVerStr, strings.Join(targetClc, "#"))
353 paths = append(paths, hostPaths...)
354 }
355 }
356
357 return clcStr, paths
358}
359
360type jsonLibraryPath struct {
361 Host string
362 Device string
363}
364
365type jsonLibraryPaths map[string]jsonLibraryPath
366
367// convert JSON map of library paths to LibraryPaths
368func constructLibraryPaths(ctx android.PathContext, paths jsonLibraryPaths) LibraryPaths {
369 m := LibraryPaths{}
370 for lib, path := range paths {
371 m[lib] = &LibraryPath{
372 constructPath(ctx, path.Host),
373 path.Device,
374 }
375 }
376 return m
377}