#!/usr/bin/env python3 # # Copyright 2019 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. """Allots libraries to modules to be packaged into. All libraries that are depended on by a single module will be allotted to this module. All other libraries will be allotted to the closest ancestor. Example: Given the module dependency structure c / \ b d / \ a e and libraries assignment a: ['lib1.so'] e: ['lib2.so', 'lib1.so'] will make the allotment decision c: ['lib1.so'] e: ['lib2.so'] The above example is invoked via: ./allot_native_libraries \ --libraries 'a,["1.so"]' \ --libraries 'e,["2.so", "1.so"]' \ --dep c:b \ --dep b:a \ --dep c:d \ --dep d:e \ --output """ import argparse import collections import json import sys from util import build_utils def _ModuleLibrariesPair(arg): pos = arg.find(',') assert pos > 0 return (arg[:pos], arg[pos + 1:]) def _DepPair(arg): parent, child = arg.split(':') return (parent, child) def _PathFromRoot(module_tree, module): """Computes path from root to a module. Parameters: module_tree: Dictionary mapping each module to its parent. module: Module to which to compute the path. Returns: Path from root the the module. """ path = [module] while module_tree.get(module): module = module_tree[module] path = [module] + path return path def _ClosestCommonAncestor(module_tree, modules): """Computes the common ancestor of a set of modules. Parameters: module_tree: Dictionary mapping each module to its parent. modules: Set of modules for which to find the closest common ancestor. Returns: The closest common ancestor. """ paths = [_PathFromRoot(module_tree, m) for m in modules] assert len(paths) > 0 ancestor = None for level in zip(*paths): if len(set(level)) != 1: return ancestor ancestor = level[0] return ancestor def _AllotLibraries(module_tree, libraries_map): """Allot all libraries to a module. Parameters: module_tree: Dictionary mapping each module to its parent. Modules can map to None, which is considered the root of the tree. libraries_map: Dictionary mapping each library to a set of modules, which depend on the library. Returns: A dictionary mapping mapping each module name to a set of libraries allotted to the module such that libraries with multiple dependees are allotted to the closest ancestor. Raises: Exception if some libraries can only be allotted to the None root. """ allotment_map = collections.defaultdict(set) for library, modules in libraries_map.items(): ancestor = _ClosestCommonAncestor(module_tree, modules) if not ancestor: raise Exception('Cannot allot libraries for given dependency tree') allotment_map[ancestor].add(library) return allotment_map def main(args): parser = argparse.ArgumentParser() parser.add_argument( '--libraries', action='append', type=_ModuleLibrariesPair, required=True, help='A pair of module name and GN list of libraries a module depends ' 'on. Can be specified multiple times.') parser.add_argument( '--output', required=True, help='A JSON file with a key for each module mapping to a list of ' 'libraries, which should be packaged into this module.') parser.add_argument( '--dep', action='append', type=_DepPair, dest='deps', default=[], help='A pair of parent module name and child module name ' '(format: ":"). Can be specified multiple times.') options = parser.parse_args(build_utils.ExpandFileArgs(args)) options.libraries = [(m, build_utils.ParseGnList(l)) for m, l in options.libraries] # Parse input creating libraries and dependency tree. libraries_map = collections.defaultdict(set) # Maps each library to its # dependee modules. module_tree = {} # Maps each module name to its parent. for module, libraries in options.libraries: module_tree[module] = None for library in libraries: libraries_map[library].add(module) for parent, child in options.deps: if module_tree.get(child): raise Exception('%s cannot have multiple parents' % child) module_tree[child] = parent module_tree[parent] = module_tree.get(parent) # Allot all libraries to a module such that libraries with multiple dependees # are allotted to the closest ancestor. allotment_map = _AllotLibraries(module_tree, libraries_map) # The build system expects there to be a set of libraries even for the modules # that don't have any libraries allotted. for module in module_tree: # Creates missing sets because of defaultdict. allotment_map[module] = allotment_map[module] with open(options.output, 'w') as f: # Write native libraries config and ensure the output is deterministic. json.dump({m: sorted(l) for m, l in allotment_map.items()}, f, sort_keys=True, indent=2) if __name__ == '__main__': sys.exit(main(sys.argv[1:]))