gcc_solink_wrapper.py revision 342c50ce1624b485728b9a4fc41d8bbf37eb46cf
1#!/usr/bin/env python 2# Copyright 2015 The Chromium Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Runs 'ld -shared' and generates a .TOC file that's untouched when unchanged. 7 8This script exists to avoid using complex shell commands in 9gcc_toolchain.gni's tool("solink"), in case the host running the compiler 10does not have a POSIX-like shell (e.g. Windows). 11""" 12 13import argparse 14import os 15import re 16import subprocess 17import sys 18 19 20# When running on a Windows host and using a toolchain whose tools are 21# actually wrapper scripts (i.e. .bat files on Windows) rather than binary 22# executables, the "command" to run has to be prefixed with this magic. 23# The GN toolchain definitions take care of that for when GN/Ninja is 24# running the tool directly. When that command is passed in to this 25# script, it appears as a unitary string but needs to be split up so that 26# just 'cmd' is the actual command given to Python's subprocess module. 27BAT_PREFIX = 'cmd /c call ' 28 29def CommandToRun(command): 30 if command[0].startswith(BAT_PREFIX): 31 command = command[0].split(None, 3) + command[1:] 32 return command 33 34 35def CollectSONAME(args): 36 """Replaces: readelf -d $sofile | grep SONAME""" 37 toc = '' 38 readelf = subprocess.Popen(CommandToRun([args.readelf, '-d', args.sofile]), 39 stdout=subprocess.PIPE, bufsize=-1) 40 for line in readelf.stdout: 41 if 'SONAME' in line: 42 toc += line 43 return readelf.wait(), toc 44 45 46def CollectDynSym(args): 47 """Replaces: nm --format=posix -g -D $sofile | cut -f1-2 -d' '""" 48 toc = '' 49 nm = subprocess.Popen(CommandToRun([ 50 args.nm, '--format=posix', '-g', '-D', args.sofile]), 51 stdout=subprocess.PIPE, bufsize=-1) 52 for line in nm.stdout: 53 toc += ' '.join(line.split(' ', 2)[:2]) + '\n' 54 return nm.wait(), toc 55 56 57def CollectTOC(args): 58 result, toc = CollectSONAME(args) 59 if result == 0: 60 result, dynsym = CollectDynSym(args) 61 toc += dynsym 62 return result, toc 63 64 65def UpdateTOC(tocfile, toc): 66 if os.path.exists(tocfile): 67 old_toc = open(tocfile, 'r').read() 68 else: 69 old_toc = None 70 if toc != old_toc: 71 open(tocfile, 'w').write(toc) 72 73 74def main(): 75 parser = argparse.ArgumentParser(description=__doc__) 76 parser.add_argument('--readelf', 77 required=True, 78 help='The readelf binary to run', 79 metavar='PATH') 80 parser.add_argument('--nm', 81 required=True, 82 help='The nm binary to run', 83 metavar='PATH') 84 parser.add_argument('--strip', 85 help='The strip binary to run', 86 metavar='PATH') 87 parser.add_argument('--sofile', 88 required=True, 89 help='Shared object file produced by linking command', 90 metavar='FILE') 91 parser.add_argument('--tocfile', 92 required=True, 93 help='Output table-of-contents file', 94 metavar='FILE') 95 parser.add_argument('--output', 96 required=True, 97 help='Final output shared object file', 98 metavar='FILE') 99 parser.add_argument('command', nargs='+', 100 help='Linking command') 101 args = parser.parse_args() 102 103 # First, run the actual link. 104 result = subprocess.call(CommandToRun(args.command)) 105 if result != 0: 106 return result 107 108 # Next, generate the contents of the TOC file. 109 result, toc = CollectTOC(args) 110 if result != 0: 111 return result 112 113 # If there is an existing TOC file with identical contents, leave it alone. 114 # Otherwise, write out the TOC file. 115 UpdateTOC(args.tocfile, toc) 116 117 # Finally, strip the linked shared object file (if desired). 118 if args.strip: 119 result = subprocess.call(CommandToRun([args.strip, '--strip-unneeded', 120 '-o', args.output, args.sofile])) 121 122 return result 123 124 125if __name__ == "__main__": 126 sys.exit(main()) 127