Home | History | Annotate | Download | only in build
      1 #!/usr/bin/python
      2 #
      3 # libjingle
      4 # Copyright 2015 Google Inc.
      5 #
      6 # Redistribution and use in source and binary forms, with or without
      7 # modification, are permitted provided that the following conditions are met:
      8 #
      9 #  1. Redistributions of source code must retain the above copyright notice,
     10 #     this list of conditions and the following disclaimer.
     11 #  2. Redistributions in binary form must reproduce the above copyright notice,
     12 #     this list of conditions and the following disclaimer in the documentation
     13 #     and/or other materials provided with the distribution.
     14 #  3. The name of the author may not be used to endorse or promote products
     15 #     derived from this software without specific prior written permission.
     16 #
     17 # THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
     18 # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     19 # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
     20 # EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
     22 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
     23 # OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
     24 # WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
     25 # OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
     26 # ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27 
     28 """Script for merging generated iOS libraries."""
     29 
     30 import optparse
     31 import os
     32 import re
     33 import subprocess
     34 import sys
     35 
     36 
     37 def MergeLibs(lib_base_dir):
     38   """Merges generated iOS libraries for different archs.
     39 
     40   Uses libtool to generate FAT archive files for each generated library.
     41 
     42   Args:
     43     lib_base_dir: directory whose subdirectories are named by architecture and
     44                   contain the built libraries for that architecture
     45 
     46   Returns:
     47     Exit code of libtool.
     48   """
     49   output_dir_name = 'fat'
     50   archs = [arch for arch in os.listdir(lib_base_dir)
     51            if arch[:1] != '.' and arch != output_dir_name]
     52   # For each arch, find (library name, libary path) for arch. We will merge
     53   # all libraries with the same name.
     54   libs = {}
     55   for dirpath, _, filenames in os.walk(lib_base_dir):
     56     if dirpath.endswith(output_dir_name):
     57       continue
     58     for filename in filenames:
     59       if not filename.endswith('.a'):
     60         continue
     61       entry = libs.get(filename, [])
     62       entry.append(os.path.join(dirpath, filename))
     63       libs[filename] = entry
     64 
     65   orphaned_libs = {}
     66   valid_libs = {}
     67   for library, paths in libs.items():
     68     if len(paths) < len(archs):
     69       orphaned_libs[library] = paths
     70     else:
     71       valid_libs[library] = paths
     72   for library, paths in orphaned_libs.items():
     73     components = library[:-2].split('_')[:-1]
     74     found = False
     75     # Find directly matching parent libs by stripping suffix.
     76     while components and not found:
     77       parent_library = '_'.join(components) + '.a'
     78       if parent_library in valid_libs:
     79         valid_libs[parent_library].extend(paths)
     80         found = True
     81         break
     82       components = components[:-1]
     83     # Find next best match by finding parent libs with the same prefix.
     84     if not found:
     85       base_prefix = library[:-2].split('_')[0]
     86       for valid_lib, valid_paths in valid_libs.items():
     87         prefix = '_'.join(components)
     88         if valid_lib[:len(base_prefix)] == base_prefix:
     89           valid_paths.extend(paths)
     90           found = True
     91           break
     92     assert found
     93 
     94   # Create output directory.
     95   output_dir_path = os.path.join(lib_base_dir, output_dir_name)
     96   if not os.path.exists(output_dir_path):
     97     os.mkdir(output_dir_path)
     98 
     99   # Use this so libtool merged binaries are always the same.
    100   env = os.environ.copy()
    101   env['ZERO_AR_DATE'] = '1'
    102 
    103   # Ignore certain errors.
    104   libtool_re = re.compile(r'^.*libtool:.*file: .* has no symbols$')
    105 
    106   # Merge libraries using libtool.
    107   for library, paths in valid_libs.items():
    108     cmd_list = ['libtool', '-static', '-v', '-o',
    109                 os.path.join(output_dir_path, library)] + paths
    110     libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE, env=env)
    111     _, err = libtoolout.communicate()
    112     for line in err.splitlines():
    113       if not libtool_re.match(line):
    114         print >>sys.stderr, line
    115     # Unconditionally touch the output .a file on the command line if present
    116     # and the command succeeded. A bit hacky.
    117     if not libtoolout.returncode:
    118       for i in range(len(cmd_list) - 1):
    119         if cmd_list[i] == '-o' and cmd_list[i+1].endswith('.a'):
    120           os.utime(cmd_list[i+1], None)
    121           break
    122     else:
    123       return libtoolout.returncode
    124   return libtoolout.returncode
    125 
    126 
    127 def Main():
    128   parser = optparse.OptionParser()
    129   _, args = parser.parse_args()
    130   if len(args) != 1:
    131     parser.error('Error: Exactly 1 argument required.')
    132   lib_base_dir = args[0]
    133   MergeLibs(lib_base_dir)
    134 
    135 if __name__ == '__main__':
    136   sys.exit(Main())
    137