#!/usr/bin/env python # Copyright (c) 2019 The Chromium Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. # Library and tool to expand command lines that mention thin archives # into command lines that mention the contained object files. from __future__ import absolute_import from __future__ import division from __future__ import print_function from __future__ import unicode_literals import argparse import errno import io import os import re import sys COMPILER_RE = re.compile('clang') LINKER_RE = re.compile('l(?:ld|ink)') LIB_RE = re.compile('.*\\.(?:a|lib)', re.IGNORECASE) OBJ_RE = re.compile(b'(.*)\\.(o(?:bj)?)', re.IGNORECASE) THIN_AR_LFN_RE = re.compile('/([0-9]+)') def ensure_dir(path): """ Creates path as a directory if it does not already exist. """ try: os.makedirs(path) except OSError as e: if e.errno != errno.EEXIST: raise def thin_archive(path): """ Returns True if path refers to a thin archive (ar file), False if not. """ with open(path, 'rb') as f: return f.read(8) == b'!\n' def write_rsp(path, params): """ Writes params to a newly created response file at path. """ ensure_dir(os.path.basename(path)) with open(path, 'wb') as f: f.write(b'\n'.join(params)) def names_in_archive(path): """ Yields the member names in the archive file at path. """ with open(path, 'rb') as f: long_names = None f.seek(8, io.SEEK_CUR) while True: file_id = f.read(16) if len(file_id) == 0: break f.seek(32, io.SEEK_CUR) m = THIN_AR_LFN_RE.match(file_id) if long_names and m: name_pos = long(m.group(1)) name_end = long_names.find('/\n', name_pos) name = long_names[name_pos:name_end] else: name = file_id try: size = long(f.read(10)) except: sys.stderr.write('While parsing %r, pos %r\n' % (path, f.tell())) raise # Two entries are special: '/' and '//'. The former is # the symbol table, which we skip. The latter is the long # file name table, which we read. # Anything else is a filename entry which we yield. # Every file record ends with two terminating characters # which we skip. seek_distance = 2 if file_id == '/ ': # Skip symbol table. seek_distance += size + (size & 1) elif file_id == '// ': # Read long name table. f.seek(2, io.SEEK_CUR) long_names = f.read(size) seek_distance = size & 1 else: yield name f.seek(seek_distance, io.SEEK_CUR) def expand_args(args, linker_prefix=''): """ Yields the parameters in args, with thin archives replaced by a sequence of '-start-lib', the member names, and '-end-lib'. This is used to get a command line where members of thin archives are mentioned explicitly. """ for arg in args: if len(arg) > 1 and arg[0] == '@': for x in expand_rsp(arg[1:], linker_prefix): yield x elif LIB_RE.match(arg) and os.path.exists(arg) and thin_archive(arg): yield(linker_prefix + '-start-lib') for name in names_in_archive(arg): yield(os.path.dirname(arg) + '/' + name) yield(linker_prefix + '-end-lib') else: yield(arg) def expand_rsp(rspname, linker_prefix=''): """ Yields the parameters found in the response file at rspname, with thin archives replaced by a sequence of '-start-lib', the member names, and '-end-lib'. This is used to get a command line where members of thin archives are mentioned explicitly. """ with open(rspname) as f: for x in expand_args(f.read().split(), linker_prefix): yield x def main(argv): ap = argparse.ArgumentParser( description=('Expand command lines that mention thin archives into' ' command lines that mention the contained object files.'), usage='%(prog)s [options] -- command line') ap.add_argument('-o', '--output', help=('Write new command line to named file' ' instead of standard output.')) ap.add_argument('-p', '--linker-prefix', help='String to prefix linker flags with.', default='') ap.add_argument('cmdline', nargs=argparse.REMAINDER, help='Command line to expand. Should be preceeded by \'--\'.') args = ap.parse_args(argv[1:]) if not args.cmdline: ap.print_help(sys.stderr) return 1 cmdline = args.cmdline if cmdline[0] == '--': cmdline = cmdline[1:] linker_prefix = args.linker_prefix if args.output: output = open(args.output, 'w') else: output = sys.stdout for arg in expand_args(cmdline, linker_prefix=linker_prefix): output.write('%s\n' % (arg,)) if args.output: output.close() return 0 if __name__ == '__main__': sys.exit(main(sys.argv))