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