Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -75,5 +75,8 @@ strict_optional = True
[mypy-mesonbuild.programs]
strict_optional = True

[mypy-mesonbuild.scripts.symbolextractor]
strict_optional = True

[mypy-mesonbuild.tooldetect]
strict_optional = True
93 changes: 51 additions & 42 deletions mesonbuild/scripts/symbolextractor.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,14 @@
from ..mesonlib import Popen_safe
import argparse

parser = argparse.ArgumentParser()
if T.TYPE_CHECKING:
class Arguments(T.Protocol):

parser.add_argument('--cross-host', default=None, dest='cross_host',
help='cross compilation host platform')
parser.add_argument('args', nargs='+')
cross_host: str | None
builddir: str
libfilename: str
impfilename: str
outfilename: str

TOOL_WARNING_FILE: str
RELINKING_WARNING = 'Relinking will always happen on source changes.'
Expand Down Expand Up @@ -60,7 +63,7 @@ def get_tool(name: str) -> T.List[str]:
return shlex.split(os.environ[evar])
return [name]

def call_tool(name: str, args: T.List[str], **kwargs: T.Any) -> str:
def call_tool(name: str, args: T.List[str], **kwargs: T.Any) -> str | None:
tool = get_tool(name)
try:
p, output, e = Popen_safe(tool + args, **kwargs)
Expand All @@ -75,7 +78,7 @@ def call_tool(name: str, args: T.List[str], **kwargs: T.Any) -> str:
return None
return output

def call_tool_nowarn(tool: T.List[str], **kwargs: T.Any) -> T.Tuple[str, str]:
def call_tool_nowarn(tool: T.List[str], **kwargs: T.Any) -> T.Tuple[str | None, str | None]:
try:
p, output, e = Popen_safe(tool, **kwargs)
except FileNotFoundError:
Expand Down Expand Up @@ -198,7 +201,7 @@ def cygwin_syms(impfilename: str, outfilename: str) -> None:
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]:
def _get_implib_dllname(impfilename: str) -> T.Tuple[T.List[str], str | None]:
all_stderr = ''
# First try lib.exe, which is provided by MSVC. Then llvm-lib.exe, by LLVM
# for clang-cl.
Expand All @@ -214,15 +217,17 @@ def _get_implib_dllname(impfilename: str) -> T.Tuple[T.List[str], str]:
# 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
if e:
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
if e:
all_stderr += e
return ([], all_stderr)

def _get_implib_exports(impfilename: str) -> T.Tuple[T.List[str], str]:
def _get_implib_exports(impfilename: str) -> T.Tuple[T.List[str], str | None]:
all_stderr = ''
# Force dumpbin.exe to use en-US so we can parse its output
env = os.environ.copy()
Expand All @@ -233,7 +238,8 @@ def _get_implib_exports(impfilename: str) -> T.Tuple[T.List[str], str]:
start = lines.index('File Type: LIBRARY')
end = lines.index(' Summary')
return lines[start:end], None
all_stderr += e
if e:
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',
Expand All @@ -245,7 +251,8 @@ def _get_implib_exports(impfilename: str) -> T.Tuple[T.List[str], str]:
continue
result.append(line.split(maxsplit=1)[0])
return result, None
all_stderr += e
if e:
all_stderr += e
return ([], all_stderr)

def windows_syms(impfilename: str, outfilename: str) -> None:
Expand Down Expand Up @@ -285,69 +292,71 @@ def os2_syms(impfilename: str, outfilename: str) -> None:
all_stderr += e
print_tool_warning(['nm', 'listomf'], 'do not work or were not found', all_stderr)

def gen_symbols(libfilename: str, impfilename: str, outfilename: str, cross_host: str) -> None:
if cross_host is not None:
def gen_symbols(options: Arguments) -> None:
if options.cross_host is not None:
# 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.
if cross_host == 'windows' and os.path.isfile(impfilename):
windows_syms(impfilename, outfilename)
if options.cross_host == 'windows' and os.path.isfile(options.impfilename):
windows_syms(options.impfilename, options.outfilename)
else:
dummy_syms(outfilename)
dummy_syms(options.outfilename)
elif mesonlib.is_linux() or mesonlib.is_hurd() or mesonlib.is_haiku():
gnu_syms(libfilename, outfilename)
gnu_syms(options.libfilename, options.outfilename)
elif mesonlib.is_osx():
osx_syms(libfilename, outfilename)
osx_syms(options.libfilename, options.outfilename)
elif mesonlib.is_openbsd():
openbsd_syms(libfilename, outfilename)
openbsd_syms(options.libfilename, options.outfilename)
elif mesonlib.is_freebsd():
freebsd_syms(libfilename, outfilename)
freebsd_syms(options.libfilename, options.outfilename)
elif mesonlib.is_netbsd():
freebsd_syms(libfilename, outfilename)
freebsd_syms(options.libfilename, options.outfilename)
elif mesonlib.is_windows():
if os.path.isfile(impfilename):
windows_syms(impfilename, outfilename)
if os.path.isfile(options.impfilename):
windows_syms(options.impfilename, options.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)
dummy_syms(options.outfilename)
elif mesonlib.is_cygwin():
if os.path.isfile(impfilename):
cygwin_syms(impfilename, outfilename)
if os.path.isfile(options.impfilename):
cygwin_syms(options.impfilename, options.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)
dummy_syms(options.outfilename)
elif mesonlib.is_sunos():
solaris_syms(libfilename, outfilename)
solaris_syms(options.libfilename, options.outfilename)
elif mesonlib.is_os2():
if os.path.isfile(impfilename):
os2_syms(impfilename, outfilename)
if os.path.isfile(options.impfilename):
os2_syms(options.impfilename, options.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)
dummy_syms(options.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', encoding='utf-8'):
pass
dummy_syms(outfilename)
dummy_syms(options.outfilename)

def run(args: T.List[str]) -> int:
global TOOL_WARNING_FILE # pylint: disable=global-statement
options = parser.parse_args(args)
if len(options.args) != 4:
print('symbolextractor.py <shared library file> <import library> <output file>')
sys.exit(1)
privdir = os.path.join(options.args[0], 'meson-private')

parser = argparse.ArgumentParser()
parser.add_argument('--cross-host', default=None, dest='cross_host',
help='cross compilation host platform')
parser.add_argument('builddir', metavar='build directory')
parser.add_argument('libfilename', metavar='shared library')
parser.add_argument('impfilename', metavar='import library')
parser.add_argument('outfilename', metavar='output')
options = T.cast('Arguments', parser.parse_args(args))
privdir = os.path.join(options.builddir, '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)
gen_symbols(options)
return 0

if __name__ == '__main__':
Expand Down
Loading