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