187 lines
5.3 KiB
Python
Executable File
187 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
#
|
|
# Copyright 2019 The Chromium Authors
|
|
# 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
|
|
import action_helpers # build_utils adds //build to sys.path.
|
|
|
|
|
|
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, action_helpers.parse_gn_list(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:]))
|