aboutsummaryrefslogtreecommitdiff
path: root/mesonbuild/scripts
diff options
context:
space:
mode:
Diffstat (limited to 'mesonbuild/scripts')
-rw-r--r--mesonbuild/scripts/symbolextractor.py229
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__':