#!/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 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 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 [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 # 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 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 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()