| #!/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 <output JSON> |
| """ |
| |
| 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: "<parent>:<child>"). 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:])) |