merge_archives.py 5.3 KB

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