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 30import optparse 31import os 32import re 33import subprocess 34import sys 35 36 37def 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 127def 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 135if __name__ == '__main__': 136 sys.exit(Main()) 137