1#!/usr/bin/env python
2#
3# Copyright 2013 The Chromium Authors. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""Writes dependency ordered list of native libraries.
8
9The list excludes any Android system libraries, as those are not bundled with
10the APK.
11
12This list of libraries is used for several steps of building an APK.
13In the component build, the --input-libraries only needs to be the top-level
14library (i.e. libcontent_shell_content_view). This will then use readelf to
15inspect the shared libraries and determine the full list of (non-system)
16libraries that should be included in the APK.
17"""
18
19# TODO(cjhopman): See if we can expose the list of library dependencies from
20# gyp, rather than calculating it ourselves.
21# http://crbug.com/225558
22
23import json
24import optparse
25import os
26import re
27import sys
28
29from util import build_utils
30
31_options = None
32_library_re = re.compile(
33    '.*NEEDED.*Shared library: \[(?P<library_name>[\w/.]+)\]')
34
35
36def FullLibraryPath(library_name):
37  return '%s/%s' % (_options.libraries_dir, library_name)
38
39
40def IsSystemLibrary(library_name):
41  # If the library doesn't exist in the libraries directory, assume that it is
42  # an Android system library.
43  return not os.path.exists(FullLibraryPath(library_name))
44
45
46def CallReadElf(library_or_executable):
47  readelf_cmd = [_options.readelf,
48                 '-d',
49                 library_or_executable]
50  return build_utils.CheckOutput(readelf_cmd)
51
52
53def GetDependencies(library_or_executable):
54  elf = CallReadElf(library_or_executable)
55  return set(_library_re.findall(elf))
56
57
58def GetNonSystemDependencies(library_name):
59  all_deps = GetDependencies(FullLibraryPath(library_name))
60  return set((lib for lib in all_deps if not IsSystemLibrary(lib)))
61
62
63def GetSortedTransitiveDependencies(libraries):
64  """Returns all transitive library dependencies in dependency order."""
65  def GraphNode(library):
66    return (library, GetNonSystemDependencies(library))
67
68  # First: find all library dependencies.
69  unchecked_deps = libraries
70  all_deps = set(libraries)
71  while unchecked_deps:
72    lib = unchecked_deps.pop()
73    new_deps = GetNonSystemDependencies(lib).difference(all_deps)
74    unchecked_deps.extend(new_deps)
75    all_deps = all_deps.union(new_deps)
76
77  # Then: simple, slow topological sort.
78  sorted_deps = []
79  unsorted_deps = dict(map(GraphNode, all_deps))
80  while unsorted_deps:
81    for library, dependencies in unsorted_deps.items():
82      if not dependencies.intersection(unsorted_deps.keys()):
83        sorted_deps.append(library)
84        del unsorted_deps[library]
85
86  return sorted_deps
87
88def GetSortedTransitiveDependenciesForExecutable(executable):
89  """Returns all transitive library dependencies in dependency order."""
90  all_deps = GetDependencies(executable)
91  libraries = [lib for lib in all_deps if not IsSystemLibrary(lib)]
92  return GetSortedTransitiveDependencies(libraries)
93
94
95def main(argv):
96  parser = optparse.OptionParser()
97
98  parser.add_option('--input-libraries',
99      help='A list of top-level input libraries.')
100  parser.add_option('--libraries-dir',
101      help='The directory which contains shared libraries.')
102  parser.add_option('--readelf', help='Path to the readelf binary.')
103  parser.add_option('--output', help='Path to the generated .json file.')
104  parser.add_option('--stamp', help='Path to touch on success.')
105
106  global _options
107  _options, _ = parser.parse_args()
108
109  libraries = build_utils.ParseGypList(_options.input_libraries)
110  if libraries[0].endswith('.so'):
111    libraries = [os.path.basename(lib) for lib in libraries]
112    libraries = GetSortedTransitiveDependencies(libraries)
113  else:
114    libraries = GetSortedTransitiveDependenciesForExecutable(libraries[0])
115
116  build_utils.WriteJson(libraries, _options.output, only_if_changed=True)
117
118  if _options.stamp:
119    build_utils.Touch(_options.stamp)
120
121
122if __name__ == '__main__':
123  sys.exit(main(sys.argv))
124
125
126