diff options
Diffstat (limited to 'mesonbuild/scripts')
-rw-r--r-- | mesonbuild/scripts/symbolextractor.py | 229 |
1 files changed, 191 insertions, 38 deletions
diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index 410cb33..65b2189 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -20,8 +20,10 @@ # This file is basically a reimplementation of # http://cgit.freedesktop.org/libreoffice/core/commit/?id=3213cd54b76bc80a6f0516aac75a48ff3b2ad67c +import typing as T import os, sys from .. import mesonlib +from .. import mlog from ..mesonlib import Popen_safe import argparse @@ -31,12 +33,15 @@ parser.add_argument('--cross-host', default=None, dest='cross_host', help='cross compilation host platform') parser.add_argument('args', nargs='+') -def dummy_syms(outfilename): +TOOL_WARNING_FILE = None +RELINKING_WARNING = 'Relinking will always happen on source changes.' + +def dummy_syms(outfilename: str): """Just touch it so relinking happens always.""" with open(outfilename, 'w'): pass -def write_if_changed(text, outfilename): +def write_if_changed(text: str, outfilename: str): try: with open(outfilename, 'r') as f: oldtext = f.read() @@ -47,27 +52,62 @@ def write_if_changed(text, outfilename): with open(outfilename, 'w') as f: f.write(text) -def linux_syms(libfilename, outfilename): - evar = 'READELF' - if evar in os.environ: - readelfbin = os.environ[evar].strip() - else: - readelfbin = 'readelf' - evar = 'NM' +def print_tool_warning(tool: list, msg: str, stderr: str = None): + global TOOL_WARNING_FILE + if os.path.exists(TOOL_WARNING_FILE): + return + if len(tool) == 1: + tool = tool[0] + m = '{!r} {}. {}'.format(tool, msg, RELINKING_WARNING) + if stderr: + m += '\n' + stderr + mlog.warning(m) + # Write it out so we don't warn again + with open(TOOL_WARNING_FILE, 'w'): + pass + +def get_tool(name: str) -> T.List[str]: + evar = name.upper() if evar in os.environ: - nmbin = os.environ[evar].strip() - else: - nmbin = 'nm' - pe, output = Popen_safe([readelfbin, '-d', libfilename])[0:2] - if pe.returncode != 0: - raise RuntimeError('Readelf does not work') + import shlex + return shlex.split(os.environ[evar]) + return [name] + +def call_tool(name: str, args: T.List[str], **kwargs) -> str: + tool = get_tool(name) + try: + p, output, e = Popen_safe(tool + args, **kwargs) + except FileNotFoundError: + print_tool_warning(tool, 'not found') + return None + if p.returncode != 0: + print_tool_warning(tool, 'does not work', e) + return None + return output + +def call_tool_nowarn(tool: T.List[str], **kwargs) -> T.Tuple[str, str]: + try: + p, output, e = Popen_safe(tool, **kwargs) + except FileNotFoundError: + return None, '{!r} not found\n'.format(tool[0]) + if p.returncode != 0: + return None, e + return output, None + +def linux_syms(libfilename: str, outfilename: str): + # Get the name of the library + output = call_tool('readelf', ['-d', libfilename]) + if not output: + dummy_syms(outfilename) + return result = [x for x in output.split('\n') if 'SONAME' in x] assert(len(result) <= 1) - pnm, output = Popen_safe([nmbin, '--dynamic', '--extern-only', - '--defined-only', '--format=posix', - libfilename])[0:2] - if pnm.returncode != 0: - raise RuntimeError('nm does not work.') + # Get a list of all symbols exported + output = call_tool('nm', ['--dynamic', '--extern-only', '--defined-only', + '--format=posix', libfilename]) + if not output: + dummy_syms(outfilename) + return for line in output.split('\n'): if not line: continue @@ -78,44 +118,157 @@ def linux_syms(libfilename, outfilename): result += [' '.join(entry)] write_if_changed('\n'.join(result) + '\n', outfilename) -def osx_syms(libfilename, outfilename): - pe, output = Popen_safe(['otool', '-l', libfilename])[0:2] - if pe.returncode != 0: - raise RuntimeError('Otool does not work.') +def osx_syms(libfilename: str, outfilename: str): + # Get the name of the library + output = call_tool('otool', ['-l', libfilename]) + if not output: + dummy_syms(outfilename) + return arr = output.split('\n') for (i, val) in enumerate(arr): if 'LC_ID_DYLIB' in val: match = i break result = [arr[match + 2], arr[match + 5]] # Libreoffice stores all 5 lines but the others seem irrelevant. - pnm, output = Popen_safe(['nm', '-g', '-P', libfilename])[0:2] - if pnm.returncode != 0: - raise RuntimeError('nm does not work.') - result += [' '.join(x.split()[0:2]) for x in output.split('\n') if x and not x.endswith('U')] + # Get a list of all symbols exported + output = call_tool('nm', ['--extern-only', '--defined-only', + '--format=posix', libfilename]) + if not output: + dummy_syms(outfilename) + return + result += [' '.join(x.split()[0:2]) for x in output.split('\n')] + write_if_changed('\n'.join(result) + '\n', outfilename) + +def cygwin_syms(impfilename: str, outfilename: str): + # Get the name of the library + output = call_tool('dlltool', ['-I', impfilename]) + if not output: + dummy_syms(outfilename) + return + result = [output] + # Get the list of all symbols exported + output = call_tool('nm', ['--extern-only', '--defined-only', + '--format=posix', impfilename]) + if not output: + dummy_syms(outfilename) + return + for line in output.split('\n'): + if ' T ' not in line: + continue + result.append(line.split(maxsplit=1)[0]) + write_if_changed('\n'.join(result) + '\n', outfilename) + +def _get_implib_dllname(impfilename: str) -> T.Tuple[T.List[str], str]: + all_stderr = '' + # First try lib.exe, which is provided by MSVC. Then llvm-lib.exe, by LLVM + # for clang-cl. + # + # We cannot call get_tool on `lib` because it will look at the `LIB` env + # var which is the list of library paths MSVC will search for import + # libraries while linking. + for lib in (['lib'], get_tool('llvm-lib')): + output, e = call_tool_nowarn(lib + ['-list', impfilename]) + if output: + # The output is a list of DLLs that each symbol exported by the import + # library is available in. We only build import libraries that point to + # a single DLL, so we can pick any of these. Pick the last one for + # simplicity. Also skip the last line, which is empty. + return output.split('\n')[-2:-1], None + all_stderr += e + # Next, try dlltool.exe which is provided by MinGW + output, e = call_tool_nowarn(get_tool('dlltool') + ['-I', impfilename]) + if output: + return [output], None + all_stderr += e + return ([], all_stderr) + +def _get_implib_exports(impfilename: str) -> T.Tuple[T.List[str], str]: + all_stderr = '' + # Force dumpbin.exe to use en-US so we can parse its output + env = os.environ.copy() + env['VSLANG'] = '1033' + output, e = call_tool_nowarn(get_tool('dumpbin') + ['-exports', impfilename], env=env) + if output: + lines = output.split('\n') + start = lines.index('File Type: LIBRARY') + end = lines.index(' Summary') + return lines[start:end], None + all_stderr += e + # Next, try llvm-nm.exe provided by LLVM, then nm.exe provided by MinGW + for nm in ('llvm-nm', 'nm'): + output, e = call_tool_nowarn(get_tool(nm) + ['--extern-only', '--defined-only', + '--format=posix', impfilename]) + if output: + result = [] + for line in output.split('\n'): + if ' T ' not in line or line.startswith('.text'): + continue + result.append(line.split(maxsplit=1)[0]) + return result, None + all_stderr += e + return ([], all_stderr) + +def windows_syms(impfilename: str, outfilename: str): + # Get the name of the library + result, e = _get_implib_dllname(impfilename) + if not result: + print_tool_warning('lib, llvm-lib, dlltool', 'do not work or were not found', e) + dummy_syms(outfilename) + return + # Get a list of all symbols exported + symbols, e = _get_implib_exports(impfilename) + if not symbols: + print_tool_warning('dumpbin, llvm-nm, nm', 'do not work or were not found', e) + dummy_syms(outfilename) + return + result += symbols write_if_changed('\n'.join(result) + '\n', outfilename) -def gen_symbols(libfilename, outfilename, cross_host): +def gen_symbols(libfilename: str, impfilename: str, outfilename: str, cross_host: str): if cross_host is not None: - # In case of cross builds just always relink. - # In theory we could determine the correct - # toolset but there are more important things - # to do. + # In case of cross builds just always relink. In theory we could + # determine the correct toolset, but we would need to use the correct + # `nm`, `readelf`, etc, from the cross info which requires refactoring. dummy_syms(outfilename) elif mesonlib.is_linux(): linux_syms(libfilename, outfilename) elif mesonlib.is_osx(): osx_syms(libfilename, outfilename) + elif mesonlib.is_windows(): + if os.path.isfile(impfilename): + windows_syms(impfilename, outfilename) + else: + # No import library. Not sure how the DLL is being used, so just + # rebuild everything that links to it every time. + dummy_syms(outfilename) + elif mesonlib.is_cygwin(): + if os.path.isfile(impfilename): + cygwin_syms(impfilename, outfilename) + else: + # No import library. Not sure how the DLL is being used, so just + # rebuild everything that links to it every time. + dummy_syms(outfilename) else: + if not os.path.exists(TOOL_WARNING_FILE): + mlog.warning('Symbol extracting has not been implemented for this ' + 'platform. ' + RELINKING_WARNING) + # Write it out so we don't warn again + with open(TOOL_WARNING_FILE, 'w'): + pass dummy_syms(outfilename) def run(args): + global TOOL_WARNING_FILE options = parser.parse_args(args) - if len(options.args) != 2: - print('symbolextractor.py <shared library file> <output file>') + if len(options.args) != 4: + print('symbolextractor.py <shared library file> <import library> <output file>') sys.exit(1) - libfile = options.args[0] - outfile = options.args[1] - gen_symbols(libfile, outfile, options.cross_host) + privdir = os.path.join(options.args[0], 'meson-private') + TOOL_WARNING_FILE = os.path.join(privdir, 'symbolextractor_tool_warning_printed') + libfile = options.args[1] + impfile = options.args[2] # Only used on Windows + outfile = options.args[3] + gen_symbols(libfile, impfile, outfile, options.cross_host) return 0 if __name__ == '__main__': |