1#!/usr/bin/env python
2#
3# Copyright (C) 2021 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9#      http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16#
17"""
18A tool for merging dexpreopt.config files for <uses-library> dependencies into
19the dexpreopt.config file of the library/app that uses them. This is needed to
20generate class loader context (CLC) for dexpreopt.
21
22In Make there is no topological order when processing different modules, so a
23<uses-library> dependency module may have not been processed yet by the time the
24dependent module is processed. Therefore makefiles communicate the information
25from dependencies via dexpreopt.config files and add file-level dependencies
26from a module dexpreopt.config to its dependency configs. The actual patching
27of configs is done by this script, which is called from the makefiles.
28"""
29
30from __future__ import print_function
31
32import json
33from collections import OrderedDict
34import sys
35
36
37def main():
38  """Program entry point."""
39  if len(sys.argv) < 2:
40    raise SystemExit('usage: %s <main-config> [dep-config ...]' % sys.argv[0])
41
42  # Read all JSON configs.
43  cfgs = []
44  for arg in sys.argv[1:]:
45    with open(arg, 'r') as f:
46      cfgs.append(json.load(f, object_pairs_hook=OrderedDict))
47
48  # The first config is the dexpreopted library/app, the rest are its
49  # <uses-library> dependencies.
50  cfg0 = cfgs[0]
51
52  # Put dependency configs in a map keyed on module name (for easier lookup).
53  uses_libs = {}
54  for cfg in cfgs[1:]:
55    uses_libs[cfg['Name']] = cfg
56
57  # Load the original CLC map.
58  clc_map = cfg0['ClassLoaderContexts']
59
60  # Create a new CLC map that will be a copy of the original one with patched
61  # fields from dependency dexpreopt.config files.
62  clc_map2 = OrderedDict()
63
64  # Patch CLC for each SDK version. Although this should not be necessary for
65  # compatibility libraries (so-called "conditional CLC"), because they all have
66  # known names, known paths in system/framework, and no subcontext. But keep
67  # the loop in case this changes in the future.
68  for sdk_ver in clc_map:
69    clcs = clc_map[sdk_ver]
70    clcs2 = []
71    for clc in clcs:
72      lib = clc['Name']
73      if lib in uses_libs:
74        ulib = uses_libs[lib]
75        # The real <uses-library> name (may be different from the module name).
76        clc['Name'] = ulib['ProvidesUsesLibrary']
77        # On-device (install) path to the dependency DEX jar file.
78        clc['Device'] = ulib['DexLocation']
79        # CLC of the dependency becomes a subcontext. We only need sub-CLC for
80        # 'any' version because all other versions are for compatibility
81        # libraries, which exist only for apps and not for libraries.
82        clc['Subcontexts'] = ulib['ClassLoaderContexts'].get('any')
83      else:
84        # dexpreopt.config for this <uses-library> is not among the script
85        # arguments, which may be the case with compatibility libraries that
86        # don't need patching anyway. Just use the original CLC.
87        pass
88      clcs2.append(clc)
89    clc_map2[sdk_ver] = clcs2
90
91  # Overwrite the original class loader context with the patched one.
92  cfg0['ClassLoaderContexts'] = clc_map2
93
94  # Update dexpreopt.config file.
95  with open(sys.argv[1], 'w') as f:
96    f.write(json.dumps(cfgs[0], indent=4, separators=(',', ': ')))
97
98if __name__ == '__main__':
99  main()
100