1#!/usr/bin/python
2
3#  Copyright 2016 The WebRTC project authors. All Rights Reserved.
4#
5#  Use of this source code is governed by a BSD-style license
6#  that can be found in the LICENSE file in the root of the source
7#  tree. An additional intellectual property rights grant can be found
8#  in the file PATENTS.  All contributing project authors may
9#  be found in the AUTHORS file in the root of the source tree.
10
11"""Script for merging generated iOS libraries."""
12
13import sys
14
15import argparse
16import os
17import re
18import subprocess
19
20# Valid arch subdir names.
21VALID_ARCHS = ['arm_libs', 'arm64_libs', 'ia32_libs', 'x64_libs']
22
23
24def MergeLibs(lib_base_dir):
25  """Merges generated iOS libraries for different archs.
26
27  Uses libtool to generate FAT archive files for each generated library.
28
29  Args:
30    lib_base_dir: directory whose subdirectories are named by architecture and
31                  contain the built libraries for that architecture
32
33  Returns:
34    Exit code of libtool.
35  """
36  output_dir_name = 'fat_libs'
37  archs = [arch for arch in os.listdir(lib_base_dir)
38           if arch in VALID_ARCHS]
39  # For each arch, find (library name, libary path) for arch. We will merge
40  # all libraries with the same name.
41  libs = {}
42  for lib_dir in [os.path.join(lib_base_dir, arch) for arch in VALID_ARCHS]:
43    if not os.path.exists(lib_dir):
44      continue
45    for dirpath, _, filenames in os.walk(lib_dir):
46      for filename in filenames:
47        if not filename.endswith('.a'):
48          continue
49        entry = libs.get(filename, [])
50        entry.append(os.path.join(dirpath, filename))
51        libs[filename] = entry
52  orphaned_libs = {}
53  valid_libs = {}
54  for library, paths in libs.items():
55    if len(paths) < len(archs):
56      orphaned_libs[library] = paths
57    else:
58      valid_libs[library] = paths
59  for library, paths in orphaned_libs.items():
60    components = library[:-2].split('_')[:-1]
61    found = False
62    # Find directly matching parent libs by stripping suffix.
63    while components and not found:
64      parent_library = '_'.join(components) + '.a'
65      if parent_library in valid_libs:
66        valid_libs[parent_library].extend(paths)
67        found = True
68        break
69      components = components[:-1]
70    # Find next best match by finding parent libs with the same prefix.
71    if not found:
72      base_prefix = library[:-2].split('_')[0]
73      for valid_lib, valid_paths in valid_libs.items():
74        if valid_lib[:len(base_prefix)] == base_prefix:
75          valid_paths.extend(paths)
76          found = True
77          break
78    assert found
79
80  # Create output directory.
81  output_dir_path = os.path.join(lib_base_dir, output_dir_name)
82  if not os.path.exists(output_dir_path):
83    os.mkdir(output_dir_path)
84
85  # Use this so libtool merged binaries are always the same.
86  env = os.environ.copy()
87  env['ZERO_AR_DATE'] = '1'
88
89  # Ignore certain errors.
90  libtool_re = re.compile(r'^.*libtool:.*file: .* has no symbols$')
91
92  # Merge libraries using libtool.
93  libtool_returncode = 0
94  for library, paths in valid_libs.items():
95    cmd_list = ['libtool', '-static', '-v', '-o',
96                os.path.join(output_dir_path, library)] + paths
97    libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE, env=env)
98    _, err = libtoolout.communicate()
99    for line in err.splitlines():
100      if not libtool_re.match(line):
101        print >>sys.stderr, line
102    # Unconditionally touch the output .a file on the command line if present
103    # and the command succeeded. A bit hacky.
104    libtool_returncode = libtoolout.returncode
105    if not libtool_returncode:
106      for i in range(len(cmd_list) - 1):
107        if cmd_list[i] == '-o' and cmd_list[i+1].endswith('.a'):
108          os.utime(cmd_list[i+1], None)
109          break
110  return libtool_returncode
111
112
113def Main():
114  parser_description = 'Merge WebRTC libraries.'
115  parser = argparse.ArgumentParser(description=parser_description)
116  parser.add_argument('lib_base_dir',
117                      help='Directory with built libraries. ',
118                      type=str)
119  args = parser.parse_args()
120  lib_base_dir = args.lib_base_dir
121  MergeLibs(lib_base_dir)
122
123if __name__ == '__main__':
124  sys.exit(Main())
125