| #!/usr/bin/env python |
| # |
| # Copyright (C) 2021 The Android Open Source Project |
| # |
| # Licensed under the Apache License, Version 2.0 (the "License"); |
| # you may not use this file except in compliance with the License. |
| # You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, software |
| # distributed under the License is distributed on an "AS IS" BASIS, |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| # See the License for the specific language governing permissions and |
| # limitations under the License. |
| # |
| """ |
| A tool for merging dexpreopt.config files for <uses-library> dependencies into |
| the dexpreopt.config file of the library/app that uses them. This is needed to |
| generate class loader context (CLC) for dexpreopt. |
| |
| In Make there is no topological order when processing different modules, so a |
| <uses-library> dependency module may have not been processed yet by the time the |
| dependent module is processed. Therefore makefiles communicate the information |
| from dependencies via dexpreopt.config files and add file-level dependencies |
| from a module dexpreopt.config to its dependency configs. The actual patching |
| of configs is done by this script, which is called from the makefiles. |
| """ |
| |
| from __future__ import print_function |
| |
| import json |
| from collections import OrderedDict |
| import sys |
| |
| |
| def main(): |
| """Program entry point.""" |
| if len(sys.argv) < 2: |
| raise SystemExit('usage: %s <main-config> [dep-config ...]' % sys.argv[0]) |
| |
| # Read all JSON configs. |
| cfgs = [] |
| for arg in sys.argv[1:]: |
| with open(arg, 'r') as f: |
| cfgs.append(json.load(f, object_pairs_hook=OrderedDict)) |
| |
| # The first config is the dexpreopted library/app, the rest are its |
| # <uses-library> dependencies. |
| cfg0 = cfgs[0] |
| |
| # Put dependency configs in a map keyed on module name (for easier lookup). |
| uses_libs = {} |
| for cfg in cfgs[1:]: |
| uses_libs[cfg['Name']] = cfg |
| |
| # Load the original CLC map. |
| clc_map = cfg0['ClassLoaderContexts'] |
| |
| # Create a new CLC map that will be a copy of the original one with patched |
| # fields from dependency dexpreopt.config files. |
| clc_map2 = OrderedDict() |
| |
| # Patch CLC for each SDK version. Although this should not be necessary for |
| # compatibility libraries (so-called "conditional CLC"), because they all have |
| # known names, known paths in system/framework, and no subcontext. But keep |
| # the loop in case this changes in the future. |
| for sdk_ver in clc_map: |
| clcs = clc_map[sdk_ver] |
| clcs2 = [] |
| for clc in clcs: |
| lib = clc['Name'] |
| if lib in uses_libs: |
| ulib = uses_libs[lib] |
| # The real <uses-library> name (may be different from the module name). |
| clc['Name'] = ulib['ProvidesUsesLibrary'] |
| # On-device (install) path to the dependency DEX jar file. |
| clc['Device'] = ulib['DexLocation'] |
| # CLC of the dependency becomes a subcontext. We only need sub-CLC for |
| # 'any' version because all other versions are for compatibility |
| # libraries, which exist only for apps and not for libraries. |
| clc['Subcontexts'] = ulib['ClassLoaderContexts'].get('any') |
| else: |
| # dexpreopt.config for this <uses-library> is not among the script |
| # arguments, which may be the case with compatibility libraries that |
| # don't need patching anyway. Just use the original CLC. |
| pass |
| clcs2.append(clc) |
| clc_map2[sdk_ver] = clcs2 |
| |
| # Overwrite the original class loader context with the patched one. |
| cfg0['ClassLoaderContexts'] = clc_map2 |
| |
| # Update dexpreopt.config file. |
| with open(sys.argv[1], 'w') as f: |
| f.write(json.dumps(cfgs[0], indent=4, separators=(',', ': '))) |
| |
| if __name__ == '__main__': |
| main() |