1271294Sngie#!/usr/bin/env python
2271294Sngie#===----------------------------------------------------------------------===##
3271294Sngie#
4271294Sngie# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
5271294Sngie# See https://llvm.org/LICENSE.txt for license information.
6271294Sngie# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
7271294Sngie#
8271294Sngie#===----------------------------------------------------------------------===##
9271294Sngie
10271294Sngiefrom argparse import ArgumentParser
11271294Sngiefrom ctypes.util import find_library
12271294Sngieimport distutils.spawn
13271294Sngieimport glob
14271294Sngieimport tempfile
15271294Sngieimport os
16271294Sngieimport shutil
17271294Sngieimport subprocess
18271294Sngieimport signal
19271294Sngieimport sys
20271294Sngie
21271294Sngietemp_directory_root = None
22271294Sngiedef exit_with_cleanups(status):
23271294Sngie    if temp_directory_root is not None:
24271294Sngie        shutil.rmtree(temp_directory_root)
25271294Sngie    sys.exit(status)
26271294Sngie
27271294Sngiedef print_and_exit(msg):
28271294Sngie    sys.stderr.write(msg + '\n')
29271294Sngie    exit_with_cleanups(1)
30271294Sngie
31271294Sngiedef find_and_diagnose_missing(lib, search_paths):
32271294Sngie    if os.path.exists(lib):
33271294Sngie        return os.path.abspath(lib)
34271294Sngie    if not lib.startswith('lib') or not lib.endswith('.a'):
35271294Sngie        print_and_exit(("input file '%s' not not name a static library. "
36271294Sngie                       "It should start with 'lib' and end with '.a") % lib)
37271294Sngie    for sp in search_paths:
38271294Sngie        assert type(sp) is list and len(sp) == 1
39271294Sngie        path = os.path.join(sp[0], lib)
40271294Sngie        if os.path.exists(path):
41271294Sngie            return os.path.abspath(path)
42271294Sngie    print_and_exit("input '%s' does not exist" % lib)
43271294Sngie
44271294Sngie
45271294Sngiedef execute_command(cmd, cwd=None):
46271294Sngie    """
47271294Sngie    Execute a command, capture and return its output.
48271294Sngie    """
49271294Sngie    kwargs = {
50271294Sngie        'stdin': subprocess.PIPE,
51271294Sngie        'stdout': subprocess.PIPE,
52271294Sngie        'stderr': subprocess.PIPE,
53271294Sngie        'cwd': cwd,
54271294Sngie        'universal_newlines': True
55271294Sngie    }
56271294Sngie    p = subprocess.Popen(cmd, **kwargs)
57271294Sngie    out, err = p.communicate()
58271294Sngie    exitCode = p.wait()
59271294Sngie    if exitCode == -signal.SIGINT:
60271294Sngie        raise KeyboardInterrupt
61271294Sngie    return out, err, exitCode
62271294Sngie
63271294Sngie
64271294Sngiedef execute_command_verbose(cmd, cwd=None, verbose=False):
65271294Sngie    """
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
84def 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
153if __name__ == '__main__':
154    main()
155    exit_with_cleanups(0)
156