Home | History | Annotate | Line # | Download | only in utils
      1 #!/usr/bin/env python
      2 #===----------------------------------------------------------------------===##
      3 #
      4 # Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
      5 # See https://llvm.org/LICENSE.txt for license information.
      6 # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
      7 #
      8 #===----------------------------------------------------------------------===##
      9 
     10 from argparse import ArgumentParser
     11 from ctypes.util import find_library
     12 import distutils.spawn
     13 import glob
     14 import tempfile
     15 import os
     16 import shutil
     17 import subprocess
     18 import signal
     19 import sys
     20 
     21 temp_directory_root = None
     22 def exit_with_cleanups(status):
     23     if temp_directory_root is not None:
     24         shutil.rmtree(temp_directory_root)
     25     sys.exit(status)
     26 
     27 def print_and_exit(msg):
     28     sys.stderr.write(msg + '\n')
     29     exit_with_cleanups(1)
     30 
     31 def find_and_diagnose_missing(lib, search_paths):
     32     if os.path.exists(lib):
     33         return os.path.abspath(lib)
     34     if not lib.startswith('lib') or not lib.endswith('.a'):
     35         print_and_exit(("input file '%s' not not name a static library. "
     36                        "It should start with 'lib' and end with '.a") % lib)
     37     for sp in search_paths:
     38         assert type(sp) is list and len(sp) == 1
     39         path = os.path.join(sp[0], lib)
     40         if os.path.exists(path):
     41             return os.path.abspath(path)
     42     print_and_exit("input '%s' does not exist" % lib)
     43 
     44 
     45 def execute_command(cmd, cwd=None):
     46     """
     47     Execute a command, capture and return its output.
     48     """
     49     kwargs = {
     50         'stdin': subprocess.PIPE,
     51         'stdout': subprocess.PIPE,
     52         'stderr': subprocess.PIPE,
     53         'cwd': cwd,
     54         'universal_newlines': True
     55     }
     56     p = subprocess.Popen(cmd, **kwargs)
     57     out, err = p.communicate()
     58     exitCode = p.wait()
     59     if exitCode == -signal.SIGINT:
     60         raise KeyboardInterrupt
     61     return out, err, exitCode
     62 
     63 
     64 def execute_command_verbose(cmd, cwd=None, verbose=False):
     65     """
     66     Execute a command and print its output on failure.
     67     """
     68     out, err, exitCode = execute_command(cmd, cwd=cwd)
     69     if exitCode != 0 or verbose:
     70         report = "Command: %s\n" % ' '.join(["'%s'" % a for a in cmd])
     71         if exitCode != 0:
     72             report += "Exit Code: %d\n" % exitCode
     73         if out:
     74             report += "Standard Output:\n--\n%s--" % out
     75         if err:
     76             report += "Standard Error:\n--\n%s--" % err
     77         if exitCode != 0:
     78             report += "\n\nFailed!"
     79         sys.stderr.write('%s\n' % report)
     80         if exitCode != 0:
     81             exit_with_cleanups(exitCode)
     82     return out
     83 
     84 def main():
     85     parser = ArgumentParser(
     86         description="Merge multiple archives into a single library")
     87     parser.add_argument(
     88         '-v', '--verbose', dest='verbose', action='store_true', default=False)
     89     parser.add_argument(
     90         '-o', '--output', dest='output', required=True,
     91         help='The output file. stdout is used if not given',
     92         type=str, action='store')
     93     parser.add_argument(
     94         '-L', dest='search_paths',
     95         help='Paths to search for the libraries along', action='append',
     96         nargs=1, default=[])
     97     parser.add_argument(
     98         '--ar', dest='ar_exe', required=False,
     99         help='The ar executable to use, finds \'ar\' in the path if not given',
    100         type=str, action='store')
    101     parser.add_argument(
    102         '--use-libtool', dest='use_libtool', action='store_true', default=False)
    103     parser.add_argument(
    104         '--libtool', dest='libtool_exe', required=False,
    105         help='The libtool executable to use, finds \'libtool\' in the path if not given',
    106         type=str, action='store')
    107     parser.add_argument(
    108         'archives', metavar='archives',  nargs='+',
    109         help='The archives to merge')
    110 
    111     args = parser.parse_args()
    112 
    113     ar_exe = args.ar_exe
    114     if not ar_exe:
    115         ar_exe = distutils.spawn.find_executable('ar')
    116     if not ar_exe:
    117         print_and_exit("failed to find 'ar' executable")
    118 
    119     if args.use_libtool:
    120         libtool_exe = args.libtool_exe
    121         if not libtool_exe:
    122             libtool_exe = distutils.spawn.find_executable('libtool')
    123         if not libtool_exe:
    124             print_and_exit("failed to find 'libtool' executable")
    125 
    126     if len(args.archives) < 2:
    127         print_and_exit('fewer than 2 inputs provided')
    128     archives = [find_and_diagnose_missing(ar, args.search_paths)
    129                 for ar in args.archives]
    130     print ('Merging archives: %s' % archives)
    131     if not os.path.exists(os.path.dirname(args.output)):
    132         print_and_exit("output path doesn't exist: '%s'" % args.output)
    133 
    134     global temp_directory_root
    135     temp_directory_root = tempfile.mkdtemp('.libcxx.merge.archives')
    136 
    137     files = []
    138     for arc in archives:
    139         execute_command_verbose([ar_exe, 'x', arc],
    140                                 cwd=temp_directory_root, verbose=args.verbose)
    141         out = execute_command_verbose([ar_exe, 't', arc])
    142         files.extend(out.splitlines())
    143 
    144     if args.use_libtool:
    145         files = [f for f in files if not f.startswith('__.SYMDEF')]
    146         execute_command_verbose([libtool_exe, '-static', '-o', args.output] + files,
    147                                 cwd=temp_directory_root, verbose=args.verbose)
    148     else:
    149         execute_command_verbose([ar_exe, 'rcs', args.output] + files,
    150                                 cwd=temp_directory_root, verbose=args.verbose)
    151 
    152 
    153 if __name__ == '__main__':
    154     main()
    155     exit_with_cleanups(0)
    156