Merge pull request #6637 from mesonbuild/nirbheek/implement-symbolextractor-windows

Implement symbolextractor on windows + some cleanups/fixes
pull/6685/head
Jussi Pakkanen 4 years ago committed by GitHub
commit 9c604320a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      ci/azure-steps.yml
  2. 10
      docs/markdown/snippets/ninja_version_bump.md
  3. 89
      mesonbuild/backend/ninjabackend.py
  4. 3
      mesonbuild/compilers/compilers.py
  5. 4
      mesonbuild/environment.py
  6. 15
      mesonbuild/linkers.py
  7. 229
      mesonbuild/scripts/symbolextractor.py
  8. 3
      mesonbuild/templates/cpptemplates.py
  9. 3
      mesonbuild/templates/ctemplates.py
  10. 3
      mesonbuild/templates/cudatemplates.py
  11. 3
      mesonbuild/templates/dlangtemplates.py
  12. 3
      mesonbuild/templates/fortrantemplates.py
  13. 1
      mesonbuild/templates/javatemplates.py
  14. 3
      mesonbuild/templates/objcpptemplates.py
  15. 3
      mesonbuild/templates/objctemplates.py
  16. 3
      mesonbuild/templates/rusttemplates.py
  17. 18
      run_tests.py
  18. 82
      run_unittests.py
  19. 2
      test cases/common/149 recursive linking/3rdorderdeps/meson.build

@ -114,8 +114,8 @@ steps:
$env:Path = $origPath
# install llvm for clang-cl builds
DownloadFile -Source 'http://releases.llvm.org/7.0.0/LLVM-7.0.0-win64.exe' -Destination LLVM-7.0.0-win64.exe
Start-Process .\LLVM-7.0.0-win64.exe -ArgumentList '/S' -Wait
DownloadFile -Source 'http://releases.llvm.org/9.0.0/LLVM-9.0.0-win64.exe' -Destination LLVM-9.0.0-win64.exe
Start-Process .\LLVM-9.0.0-win64.exe -ArgumentList '/S' -Wait
$env:Path = "C:\Program Files\LLVM\bin;$env:Path"
$env:CC = "clang-cl"
$env:CXX = "clang-cl"

@ -0,0 +1,10 @@
## Ninja version requirement bumped to 1.7
Meson now uses the [Implicit outputs](https://ninja-build.org/manual.html#ref_outputs)
feature of Ninja for some types of targets that have multiple outputs which may
not be listed on the command-line. This feature requires Ninja 1.7+.
Note that the latest version of [Ninja available in Ubuntu 16.04](https://packages.ubuntu.com/search?keywords=ninja-build&searchon=names&suite=xenial-backports&section=all)
(the oldest Ubuntu LTS at the time of writing) is 1.7.1. If your distro does
not ship with a new-enough Ninja, you can download the latest release from
Ninja's GitHub page: https://github.com/ninja-build/ninja/releases

@ -120,7 +120,8 @@ class NinjaRule:
outfile.write('\n')
class NinjaBuildElement:
def __init__(self, all_outputs, outfilenames, rule, infilenames):
def __init__(self, all_outputs, outfilenames, rule, infilenames, implicit_outs=None):
self.implicit_outfilenames = implicit_outs or []
if isinstance(outfilenames, str):
self.outfilenames = [outfilenames]
else:
@ -155,9 +156,12 @@ class NinjaBuildElement:
def write(self, outfile):
self.check_outputs()
line = 'build %s: %s %s' % (' '.join([ninja_quote(i, True) for i in self.outfilenames]),
self.rule,
' '.join([ninja_quote(i, True) for i in self.infilenames]))
ins = ' '.join([ninja_quote(i, True) for i in self.infilenames])
outs = ' '.join([ninja_quote(i, True) for i in self.outfilenames])
implicit_outs = ' '.join([ninja_quote(i, True) for i in self.implicit_outfilenames])
if implicit_outs:
implicit_outs = ' | ' + implicit_outs
line = 'build {}{}: {} {}'.format(outs, implicit_outs, self.rule, ins)
if len(self.deps) > 0:
line += ' | ' + ' '.join([ninja_quote(x, True) for x in self.deps])
if len(self.orderdeps) > 0:
@ -664,7 +668,7 @@ int dummy;
(srcs, ofilenames, cmd) = self.eval_custom_target_command(target)
deps = self.unwrap_dep_list(target)
deps += self.get_custom_target_depend_files(target)
desc = 'Generating {0} with a {1} command.'
desc = 'Generating {0} with a {1} command'
if target.build_always_stale:
deps.append('PHONY')
if target.depfile is None:
@ -760,7 +764,7 @@ int dummy;
target_name = 'meson-{}'.format(self.build_run_target_name(target))
elem = NinjaBuildElement(self.all_outputs, target_name, 'CUSTOM_COMMAND', [])
elem.add_item('COMMAND', cmd)
elem.add_item('description', 'Running external command %s.' % target.name)
elem.add_item('description', 'Running external command %s' % target.name)
elem.add_item('pool', 'console')
# Alias that runs the target defined above with the name the user specified
self.create_target_alias(target_name)
@ -785,7 +789,7 @@ int dummy;
def generate_coverage_rules(self):
e = NinjaBuildElement(self.all_outputs, 'meson-coverage', 'CUSTOM_COMMAND', 'PHONY')
self.generate_coverage_command(e, [])
e.add_item('description', 'Generates coverage reports.')
e.add_item('description', 'Generates coverage reports')
self.add_build(e)
# Alias that runs the target defined above
self.create_target_alias('meson-coverage')
@ -794,21 +798,21 @@ int dummy;
def generate_coverage_legacy_rules(self):
e = NinjaBuildElement(self.all_outputs, 'meson-coverage-xml', 'CUSTOM_COMMAND', 'PHONY')
self.generate_coverage_command(e, ['--xml'])
e.add_item('description', 'Generates XML coverage report.')
e.add_item('description', 'Generates XML coverage report')
self.add_build(e)
# Alias that runs the target defined above
self.create_target_alias('meson-coverage-xml')
e = NinjaBuildElement(self.all_outputs, 'meson-coverage-text', 'CUSTOM_COMMAND', 'PHONY')
self.generate_coverage_command(e, ['--text'])
e.add_item('description', 'Generates text coverage report.')
e.add_item('description', 'Generates text coverage report')
self.add_build(e)
# Alias that runs the target defined above
self.create_target_alias('meson-coverage-text')
e = NinjaBuildElement(self.all_outputs, 'meson-coverage-html', 'CUSTOM_COMMAND', 'PHONY')
self.generate_coverage_command(e, ['--html'])
e.add_item('description', 'Generates HTML coverage report.')
e.add_item('description', 'Generates HTML coverage report')
self.add_build(e)
# Alias that runs the target defined above
self.create_target_alias('meson-coverage-html')
@ -975,7 +979,7 @@ int dummy;
ofilename = os.path.join(self.get_target_private_dir(target), ofilebase)
elem = NinjaBuildElement(self.all_outputs, ofilename, "CUSTOM_COMMAND", rel_sourcefile)
elem.add_item('COMMAND', ['resgen', rel_sourcefile, ofilename])
elem.add_item('DESC', 'Compiling resource %s.' % rel_sourcefile)
elem.add_item('DESC', 'Compiling resource %s' % rel_sourcefile)
self.add_build(elem)
deps.append(ofilename)
a = '-resource:' + ofilename
@ -1069,7 +1073,7 @@ int dummy;
def generate_java_link(self):
rule = 'java_LINKER'
command = ['jar', '$ARGS']
description = 'Creating JAR $out.'
description = 'Creating JAR $out'
self.add_rule(NinjaRule(rule, command, [], description))
def determine_dep_vapis(self, target):
@ -1550,7 +1554,7 @@ int dummy;
cmdlist += static_linker.get_exelist()
cmdlist += ['$LINK_ARGS']
cmdlist += static_linker.get_output_args('$out')
description = 'Linking static target $out.'
description = 'Linking static target $out'
if num_pools > 0:
pool = 'pool = link_pool'
else:
@ -1572,7 +1576,7 @@ int dummy;
rule = '%s_LINKER%s' % (langname, self.get_rule_suffix(for_machine))
command = compiler.get_linker_exelist()
args = ['$ARGS'] + compiler.get_linker_output_args('$out') + ['$in', '$LINK_ARGS']
description = 'Linking target $out.'
description = 'Linking target $out'
if num_pools > 0:
pool = 'pool = link_pool'
else:
@ -1584,11 +1588,13 @@ int dummy;
args = [ninja_quote(quote_func(x)) for x in self.environment.get_build_command()] + \
['--internal',
'symbolextractor',
ninja_quote(quote_func(self.environment.get_build_dir())),
'$in',
'$IMPLIB',
'$out']
symrule = 'SHSYM'
symcmd = args + ['$CROSS']
syndesc = 'Generating symbol file $out.'
syndesc = 'Generating symbol file $out'
synstat = 'restat = 1'
self.add_rule(NinjaRule(symrule, symcmd, [], syndesc, extra=synstat))
@ -1596,7 +1602,7 @@ int dummy;
rule = self.compiler_to_rule_name(compiler)
invoc = [ninja_quote(i) for i in compiler.get_exelist()]
command = invoc + ['$ARGS', '$in']
description = 'Compiling Java object $in.'
description = 'Compiling Java object $in'
self.add_rule(NinjaRule(rule, command, [], description))
def generate_cs_compile_rule(self, compiler):
@ -1604,7 +1610,7 @@ int dummy;
invoc = [ninja_quote(i) for i in compiler.get_exelist()]
command = invoc
args = ['$ARGS', '$in']
description = 'Compiling C Sharp target $out.'
description = 'Compiling C Sharp target $out'
self.add_rule(NinjaRule(rule, command, args, description,
rspable=mesonlib.is_windows()))
@ -1612,14 +1618,14 @@ int dummy;
rule = self.compiler_to_rule_name(compiler)
invoc = [ninja_quote(i) for i in compiler.get_exelist()]
command = invoc + ['$ARGS', '$in']
description = 'Compiling Vala source $in.'
description = 'Compiling Vala source $in'
self.add_rule(NinjaRule(rule, command, [], description, extra='restat = 1'))
def generate_rust_compile_rules(self, compiler):
rule = self.compiler_to_rule_name(compiler)
invoc = [ninja_quote(i) for i in compiler.get_exelist()]
command = invoc + ['$ARGS', '$in']
description = 'Compiling Rust source $in.'
description = 'Compiling Rust source $in'
depfile = '$targetdep'
depstyle = 'gcc'
self.add_rule(NinjaRule(rule, command, [], description, deps=depstyle,
@ -1634,7 +1640,7 @@ int dummy;
]
invoc = full_exe + [ninja_quote(i) for i in compiler.get_exelist()]
command = invoc + ['$ARGS', '$in']
description = 'Compiling Swift source $in.'
description = 'Compiling Swift source $in'
self.add_rule(NinjaRule(rule, command, [], description))
def generate_fortran_dep_hack(self, crstr):
@ -1654,7 +1660,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
rule = self.get_compiler_rule_name('llvm_ir', compiler.for_machine)
command = [ninja_quote(i) for i in compiler.get_exelist()]
args = ['$ARGS'] + compiler.get_output_args('$out') + compiler.get_compile_only_args() + ['$in']
description = 'Compiling LLVM IR object $in.'
description = 'Compiling LLVM IR object $in'
self.add_rule(NinjaRule(rule, command, args, description,
rspable=compiler.can_linker_accept_rsp()))
self.created_llvm_ir_rule[compiler.for_machine] = True
@ -1691,7 +1697,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
command = [ninja_quote(i) for i in compiler.get_exelist()]
args = ['$ARGS'] + quoted_depargs + compiler.get_output_args('$out') + compiler.get_compile_only_args() + ['$in']
description = 'Compiling %s object $out.' % compiler.get_display_language()
description = 'Compiling %s object $out' % compiler.get_display_language()
if isinstance(compiler, VisualStudioLikeCompiler):
deps = 'msvc'
depfile = None
@ -1718,7 +1724,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
else:
output = compiler.get_output_args('$out')
command = compiler.get_exelist() + ['$ARGS'] + quoted_depargs + output + compiler.get_compile_only_args() + ['$in']
description = 'Precompiling header $in.'
description = 'Precompiling header $in'
if isinstance(compiler, VisualStudioLikeCompiler):
deps = 'msvc'
depfile = None
@ -1946,6 +1952,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
else:
return compiler.get_compile_debugfile_args(objfile, pch=False)
def get_link_debugfile_name(self, linker, target, outname):
return linker.get_link_debugfile_name(outname)
def get_link_debugfile_args(self, linker, target, outname):
return linker.get_link_debugfile_args(outname)
@ -2300,12 +2309,17 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
self.add_build(elem)
return pch_objects
def get_target_shsym_filename(self, target):
# Always name the .symbols file after the primary build output because it always exists
targetdir = self.get_target_private_dir(target)
return os.path.join(targetdir, target.get_filename() + '.symbols')
def generate_shsym(self, target):
target_name = target.get_filename()
target_file = self.get_target_filename(target)
targetdir = self.get_target_private_dir(target)
symname = os.path.join(targetdir, target_name + '.symbols')
symname = self.get_target_shsym_filename(target)
elem = NinjaBuildElement(self.all_outputs, symname, 'SHSYM', target_file)
# The library we will actually link to, which is an import library on Windows (not the DLL)
elem.add_item('IMPLIB', self.get_target_filename_for_linking(target))
if self.environment.is_cross_build():
elem.add_item('CROSS', '--cross-host=' + self.environment.machines[target.for_machine].system)
self.add_build(elem)
@ -2318,6 +2332,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
return []
return linker.get_no_stdlib_link_args()
def get_import_filename(self, target):
return os.path.join(self.get_target_dir(target), target.import_filename)
def get_target_type_link_args(self, target, linker):
commands = []
if isinstance(target, build.Executable):
@ -2328,7 +2345,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
commands += linker.gen_export_dynamic_link_args(self.environment)
# If implib, and that's significant on this platform (i.e. Windows using either GCC or Visual Studio)
if target.import_filename:
commands += linker.gen_import_library_args(os.path.join(self.get_target_dir(target), target.import_filename))
commands += linker.gen_import_library_args(self.get_import_filename(target))
if target.pie:
commands += linker.get_pie_link_args()
elif isinstance(target, build.SharedLibrary):
@ -2349,7 +2366,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
commands += linker.gen_vs_module_defs_args(target.vs_module_defs.rel_to_builddir(self.build_to_src))
# This is only visited when building for Windows using either GCC or Visual Studio
if target.import_filename:
commands += linker.gen_import_library_args(os.path.join(self.get_target_dir(target), target.import_filename))
commands += linker.gen_import_library_args(self.get_import_filename(target))
elif isinstance(target, build.StaticLibrary):
commands += linker.get_std_link_args()
else:
@ -2448,6 +2465,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
def generate_link(self, target, outname, obj_list, linker, extra_args=None, stdlib_args=None):
extra_args = extra_args if extra_args is not None else []
stdlib_args = stdlib_args if stdlib_args is not None else []
implicit_outs = []
if isinstance(target, build.StaticLibrary):
linker_base = 'STATIC'
else:
@ -2483,6 +2501,9 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
# Add /DEBUG and the pdb filename when using MSVC
if self.get_option_for_target('debug', target):
commands += self.get_link_debugfile_args(linker, target, outname)
debugfile = self.get_link_debugfile_name(linker, target, outname)
if debugfile is not None:
implicit_outs += [debugfile]
# Add link args specific to this BuildTarget type, such as soname args,
# PIC, import library generation, etc.
commands += self.get_target_type_link_args(target, linker)
@ -2571,14 +2592,14 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
dep_targets.extend([self.get_dependency_filename(t) for t in dependencies])
dep_targets.extend([self.get_dependency_filename(t)
for t in target.link_depends])
elem = NinjaBuildElement(self.all_outputs, outname, linker_rule, obj_list)
elem = NinjaBuildElement(self.all_outputs, outname, linker_rule, obj_list, implicit_outs=implicit_outs)
elem.add_dep(dep_targets + custom_target_libraries)
elem.add_item('LINK_ARGS', commands)
return elem
def get_dependency_filename(self, t):
if isinstance(t, build.SharedLibrary):
return os.path.join(self.get_target_private_dir(t), t.get_filename() + '.symbols')
return self.get_target_shsym_filename(t)
elif isinstance(t, mesonlib.File):
if t.is_built:
return t.relative_name()
@ -2607,7 +2628,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
d = CleanTrees(self.environment.get_build_dir(), trees)
d_file = os.path.join(self.environment.get_scratch_dir(), 'cleantrees.dat')
e.add_item('COMMAND', self.environment.get_build_command() + ['--internal', 'cleantrees', d_file])
e.add_item('description', 'Cleaning custom target directories.')
e.add_item('description', 'Cleaning custom target directories')
self.add_build(e)
# Alias that runs the target defined above
self.create_target_alias('meson-clean-ctlist')
@ -2621,7 +2642,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
script_root = self.environment.get_script_dir()
clean_script = os.path.join(script_root, 'delwithsuffix.py')
gcno_elem.add_item('COMMAND', mesonlib.python_command + [clean_script, '.', 'gcno'])
gcno_elem.add_item('description', 'Deleting gcno files.')
gcno_elem.add_item('description', 'Deleting gcno files')
self.add_build(gcno_elem)
# Alias that runs the target defined above
self.create_target_alias('meson-clean-gcno')
@ -2630,7 +2651,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
script_root = self.environment.get_script_dir()
clean_script = os.path.join(script_root, 'delwithsuffix.py')
gcda_elem.add_item('COMMAND', mesonlib.python_command + [clean_script, '.', 'gcda'])
gcda_elem.add_item('description', 'Deleting gcda files.')
gcda_elem.add_item('description', 'Deleting gcda files')
self.add_build(gcda_elem)
# Alias that runs the target defined above
self.create_target_alias('meson-clean-gcda')
@ -2741,7 +2762,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485'''))
elem = NinjaBuildElement(self.all_outputs, 'meson-clean', 'CUSTOM_COMMAND', 'PHONY')
elem.add_item('COMMAND', [self.ninja_command, '-t', 'clean'])
elem.add_item('description', 'Cleaning.')
elem.add_item('description', 'Cleaning')
# Alias that runs the above-defined meson-clean target
self.create_target_alias('meson-clean')

@ -982,6 +982,9 @@ class Compiler:
def get_compile_debugfile_args(self, rel_obj, **kwargs):
return []
def get_link_debugfile_name(self, targetfile: str) -> str:
return self.linker.get_debugfile_name(targetfile)
def get_link_debugfile_args(self, targetfile: str) -> T.List[str]:
return self.linker.get_debugfile_args(targetfile)

@ -141,11 +141,11 @@ def find_coverage_tools():
return gcovr_exe, gcovr_new_rootdir, lcov_exe, genhtml_exe
def detect_ninja(version: str = '1.5', log: bool = False) -> str:
def detect_ninja(version: str = '1.7', log: bool = False) -> str:
r = detect_ninja_command_and_version(version, log)
return r[0] if r else None
def detect_ninja_command_and_version(version: str = '1.5', log: bool = False) -> (str, str):
def detect_ninja_command_and_version(version: str = '1.7', log: bool = False) -> (str, str):
env_ninja = os.environ.get('NINJA', None)
for n in [env_ninja] if env_ninja else ['ninja', 'ninja-build', 'samu']:
try:

@ -75,6 +75,9 @@ class StaticLinker:
def native_args_to_unix(cls, args: T.List[str]) -> T.List[str]:
return args[:]
def get_link_debugfile_name(self, targetfile: str) -> str:
return None
def get_link_debugfile_args(self, targetfile: str) -> T.List[str]:
# Static libraries do not have PDB files
return []
@ -305,6 +308,10 @@ class DynamicLinker(metaclass=abc.ABCMeta):
m = 'Language {} does not support has_multi_link_arguments.'
raise mesonlib.EnvironmentException(m.format(self.id))
def get_debugfile_name(self, targetfile: str) -> str:
'''Name of debug file written out (see below)'''
return None
def get_debugfile_args(self, targetfile: str) -> T.List[str]:
"""Some compilers (MSVC) write debug into a separate file.
@ -842,10 +849,12 @@ class VisualStudioLikeLinkerMixin:
def get_std_shared_lib_args(self) -> T.List[str]:
return self._apply_prefix('/DLL')
def get_debugfile_name(self, targetfile: str) -> str:
basename = targetfile.rsplit('.', maxsplit=1)[0]
return basename + '.pdb'
def get_debugfile_args(self, targetfile: str) -> T.List[str]:
pdbarr = targetfile.split('.')[:-1]
pdbarr += ['pdb']
return self._apply_prefix(['/DEBUG', '/PDB:' + '.'.join(pdbarr)])
return self._apply_prefix(['/DEBUG', '/PDB:' + self.get_debugfile_name(targetfile)])
def get_link_whole_for(self, args: T.List[str]) -> T.List[str]:
# Only since VS2015

@ -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__':

@ -153,6 +153,7 @@ def create_lib_cpp_sample(project_name, version):
lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
uppercase_token = lowercase_token.upper()
class_name = uppercase_token[0] + lowercase_token[1:]
test_exe_name = lowercase_token + '_test'
namespace = lowercase_token
lib_hpp_name = lowercase_token + '.hpp'
lib_cpp_name = lowercase_token + '.cpp'
@ -165,7 +166,7 @@ def create_lib_cpp_sample(project_name, version):
'header_file': lib_hpp_name,
'source_file': lib_cpp_name,
'test_source_file': test_cpp_name,
'test_exe_name': lowercase_token,
'test_exe_name': test_exe_name,
'project_name': project_name,
'lib_name': lowercase_token,
'test_name': lowercase_token,

@ -134,6 +134,7 @@ def create_lib_c_sample(project_name, version):
lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
uppercase_token = lowercase_token.upper()
function_name = lowercase_token[0:3] + '_func'
test_exe_name = lowercase_token + '_test'
lib_h_name = lowercase_token + '.h'
lib_c_name = lowercase_token + '.c'
test_c_name = lowercase_token + '_test.c'
@ -144,7 +145,7 @@ def create_lib_c_sample(project_name, version):
'header_file': lib_h_name,
'source_file': lib_c_name,
'test_source_file': test_c_name,
'test_exe_name': lowercase_token,
'test_exe_name': test_exe_name,
'project_name': project_name,
'lib_name': lowercase_token,
'test_name': lowercase_token,

@ -153,6 +153,7 @@ def create_lib_cuda_sample(project_name, version):
lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
uppercase_token = lowercase_token.upper()
class_name = uppercase_token[0] + lowercase_token[1:]
test_exe_name = lowercase_token + '_test'
namespace = lowercase_token
lib_h_name = lowercase_token + '.h'
lib_cuda_name = lowercase_token + '.cu'
@ -165,7 +166,7 @@ def create_lib_cuda_sample(project_name, version):
'header_file': lib_h_name,
'source_file': lib_cuda_name,
'test_source_file': test_cuda_name,
'test_exe_name': lowercase_token,
'test_exe_name': test_exe_name,
'project_name': project_name,
'lib_name': lowercase_token,
'test_name': lowercase_token,

@ -113,6 +113,7 @@ def create_lib_d_sample(project_name, version):
lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
uppercase_token = lowercase_token.upper()
function_name = lowercase_token[0:3] + '_func'
test_exe_name = lowercase_token + '_test'
lib_m_name = lowercase_token
lib_d_name = lowercase_token + '.d'
test_d_name = lowercase_token + '_test.d'
@ -123,7 +124,7 @@ def create_lib_d_sample(project_name, version):
'module_file': lib_m_name,
'source_file': lib_d_name,
'test_source_file': test_d_name,
'test_exe_name': lowercase_token,
'test_exe_name': test_exe_name,
'project_name': project_name,
'lib_name': lowercase_token,
'test_name': lowercase_token,

@ -111,6 +111,7 @@ def create_lib_fortran_sample(project_name, version):
lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
uppercase_token = lowercase_token.upper()
function_name = lowercase_token[0:3] + '_func'
test_exe_name = lowercase_token + '_test'
lib_fortran_name = lowercase_token + '.f90'
test_fortran_name = lowercase_token + '_test.f90'
kwargs = {'utoken': uppercase_token,
@ -119,7 +120,7 @@ def create_lib_fortran_sample(project_name, version):
'function_name': function_name,
'source_file': lib_fortran_name,
'test_source_file': test_fortran_name,
'test_exe_name': lowercase_token,
'test_exe_name': test_exe_name,
'project_name': project_name,
'lib_name': lowercase_token,
'test_name': lowercase_token,

@ -118,7 +118,6 @@ def create_lib_java_sample(project_name, version):
'class_name': class_name,
'source_file': lib_java_name,
'test_source_file': test_java_name,
'test_exe_name': lowercase_token,
'project_name': project_name,
'lib_name': lowercase_token,
'test_name': lowercase_token,

@ -134,6 +134,7 @@ def create_lib_objcpp_sample(project_name, version):
lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
uppercase_token = lowercase_token.upper()
function_name = lowercase_token[0:3] + '_func'
test_exe_name = lowercase_token + '_test'
lib_h_name = lowercase_token + '.h'
lib_objcpp_name = lowercase_token + '.mm'
test_objcpp_name = lowercase_token + '_test.mm'
@ -144,7 +145,7 @@ def create_lib_objcpp_sample(project_name, version):
'header_file': lib_h_name,
'source_file': lib_objcpp_name,
'test_source_file': test_objcpp_name,
'test_exe_name': lowercase_token,
'test_exe_name': test_exe_name,
'project_name': project_name,
'lib_name': lowercase_token,
'test_name': lowercase_token,

@ -134,6 +134,7 @@ def create_lib_objc_sample(project_name, version):
lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
uppercase_token = lowercase_token.upper()
function_name = lowercase_token[0:3] + '_func'
test_exe_name = lowercase_token + '_test'
lib_h_name = lowercase_token + '.h'
lib_objc_name = lowercase_token + '.m'
test_objc_name = lowercase_token + '_test.m'
@ -144,7 +145,7 @@ def create_lib_objc_sample(project_name, version):
'header_file': lib_h_name,
'source_file': lib_objc_name,
'test_source_file': test_objc_name,
'test_exe_name': lowercase_token,
'test_exe_name': test_exe_name,
'project_name': project_name,
'lib_name': lowercase_token,
'test_name': lowercase_token,

@ -82,6 +82,7 @@ def create_lib_rust_sample(project_name, version):
lowercase_token = re.sub(r'[^a-z0-9]', '_', project_name.lower())
uppercase_token = lowercase_token.upper()
function_name = lowercase_token[0:3] + '_func'
test_exe_name = lowercase_token + '_test'
lib_crate_name = lowercase_token
lib_rs_name = lowercase_token + '.rs'
test_rs_name = lowercase_token + '_test.rs'
@ -92,7 +93,7 @@ def create_lib_rust_sample(project_name, version):
'crate_file': lib_crate_name,
'source_file': lib_rs_name,
'test_source_file': test_rs_name,
'test_exe_name': lowercase_token,
'test_exe_name': test_exe_name,
'project_name': project_name,
'lib_name': lowercase_token,
'test_name': lowercase_token,

@ -45,17 +45,15 @@ if 'CI' in os.environ:
NINJA_CMD = 'ninja'
else:
# Look for 1.9 to see if https://github.com/ninja-build/ninja/issues/1219
# is fixed, else require 1.6 for -w dupbuild=err
for v in ('1.9', '1.6'):
NINJA_CMD = detect_ninja(v)
if NINJA_CMD is not None:
if mesonlib.version_compare(v, '>=1.9'):
NINJA_1_9_OR_NEWER = True
else:
mlog.warning('Found ninja <1.9, tests will run slower', once=True)
break
# is fixed
NINJA_CMD = detect_ninja('1.9')
if NINJA_CMD is not None:
NINJA_1_9_OR_NEWER = True
else:
mlog.warning('Found ninja <1.9, tests will run slower', once=True)
NINJA_CMD = detect_ninja()
if NINJA_CMD is None:
raise RuntimeError('Could not find Ninja v1.6 or newer')
raise RuntimeError('Could not find Ninja v1.7 or newer')
def guess_backend(backend, msbuild_exe: str):
# Auto-detect backend if unspecified

@ -809,6 +809,7 @@ class InternalTests(unittest.TestCase):
env.machines.host.system = 'windows'
self._test_all_naming(cc, env, patterns, 'windows-mingw')
@skipIfNoPkgconfig
def test_pkgconfig_parse_libs(self):
'''
Unit test for parsing of pkg-config output to search for libraries
@ -1700,20 +1701,43 @@ class BasePlatformTests(unittest.TestCase):
path_basename = PurePath(path).parts[-1]
self.assertEqual(PurePath(path_basename), PurePath(basename), msg)
def assertReconfiguredBuildIsNoop(self):
'Assert that we reconfigured and then there was nothing to do'
ret = self.build()
self.assertIn('The Meson build system', ret)
if self.backend is Backend.ninja:
for line in ret.split('\n'):
if line in self.no_rebuild_stdout:
break
else:
raise AssertionError('build was reconfigured, but was not no-op')
elif self.backend is Backend.vs:
# Ensure that some target said that no rebuild was done
# XXX: Note CustomBuild did indeed rebuild, because of the regen checker!
self.assertIn('ClCompile:\n All outputs are up-to-date.', ret)
self.assertIn('Link:\n All outputs are up-to-date.', ret)
# Ensure that no targets were built
self.assertNotRegex(ret, re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE))
self.assertNotRegex(ret, re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE))
elif self.backend is Backend.xcode:
raise unittest.SkipTest('Please help us fix this test on the xcode backend')
else:
raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name))
def assertBuildIsNoop(self):
ret = self.build()
if self.backend is Backend.ninja:
self.assertIn(ret.split('\n')[-2], self.no_rebuild_stdout)
elif self.backend is Backend.vs:
# Ensure that some target said that no rebuild was done
# Ensure that some target of each type said that no rebuild was done
# We always have at least one CustomBuild target for the regen checker
self.assertIn('CustomBuild:\n All outputs are up-to-date.', ret)
self.assertIn('ClCompile:\n All outputs are up-to-date.', ret)
self.assertIn('Link:\n All outputs are up-to-date.', ret)
# Ensure that no targets were built
clre = re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE)
linkre = re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE)
self.assertNotRegex(ret, clre)
self.assertNotRegex(ret, linkre)
self.assertNotRegex(ret, re.compile('CustomBuild:\n [^\n]*cl', flags=re.IGNORECASE))
self.assertNotRegex(ret, re.compile('ClCompile:\n [^\n]*cl', flags=re.IGNORECASE))
self.assertNotRegex(ret, re.compile('Link:\n [^\n]*link', flags=re.IGNORECASE))
elif self.backend is Backend.xcode:
raise unittest.SkipTest('Please help us fix this test on the xcode backend')
else:
@ -1732,6 +1756,33 @@ class BasePlatformTests(unittest.TestCase):
else:
raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name))
@staticmethod
def get_target_from_filename(filename):
base = os.path.splitext(filename)[0]
if base.startswith(('lib', 'cyg')):
return base[3:]
return base
def assertBuildRelinkedOnlyTarget(self, target):
ret = self.build()
if self.backend is Backend.ninja:
linked_targets = []
for line in ret.split('\n'):
if 'Linking target' in line:
fname = line.rsplit('target ')[-1]
linked_targets.append(self.get_target_from_filename(fname))
self.assertEqual(linked_targets, [target])
elif self.backend is Backend.vs:
# Ensure that this target was rebuilt
linkre = re.compile(r'Link:\n [^\n]*link.exe[^\n]*/OUT:".\\([^"]*)"', flags=re.IGNORECASE)
matches = linkre.findall(ret)
self.assertEqual(len(matches), 1, msg=matches)
self.assertEqual(self.get_target_from_filename(matches[0]), target)
elif self.backend is Backend.xcode:
raise unittest.SkipTest('Please help us fix this test on the xcode backend')
else:
raise RuntimeError('Invalid backend: {!r}'.format(self.backend.name))
def assertPathExists(self, path):
m = 'Path {!r} should exist'.format(path)
self.assertTrue(os.path.exists(path), msg=m)
@ -2535,6 +2586,23 @@ class AllPlatformTests(BasePlatformTests):
meson_exe_dat2 = glob(os.path.join(self.privatedir, 'meson_exe*.dat'))
self.assertListEqual(meson_exe_dat1, meson_exe_dat2)
def test_noop_changes_cause_no_rebuilds(self):
'''
Test that no-op changes to the build files such as mtime do not cause
a rebuild of anything.
'''
testdir = os.path.join(self.common_test_dir, '6 linkshared')
self.init(testdir)
self.build()
# Immediately rebuilding should not do anything
self.assertBuildIsNoop()
# Changing mtime of meson.build should not rebuild anything
self.utime(os.path.join(testdir, 'meson.build'))
self.assertReconfiguredBuildIsNoop()
# Changing mtime of libefile.c should rebuild the library, but not relink the executable
self.utime(os.path.join(testdir, 'libfile.c'))
self.assertBuildRelinkedOnlyTarget('mylib')
def test_source_changes_cause_rebuild(self):
'''
Test that changes to sources and headers cause rebuilds, but not
@ -2548,7 +2616,7 @@ class AllPlatformTests(BasePlatformTests):
self.assertBuildIsNoop()
# Changing mtime of header.h should rebuild everything
self.utime(os.path.join(testdir, 'header.h'))
self.assertRebuiltTarget('prog')
self.assertBuildRelinkedOnlyTarget('prog')
def test_custom_target_changes_cause_rebuild(self):
'''
@ -2564,7 +2632,7 @@ class AllPlatformTests(BasePlatformTests):
# Changing mtime of these should rebuild everything
for f in ('input.def', 'makeheader.py', 'somefile.txt'):
self.utime(os.path.join(testdir, f))
self.assertRebuiltTarget('prog')
self.assertBuildRelinkedOnlyTarget('prog')
def test_source_generator_program_cause_rebuild(self):
'''

@ -41,7 +41,7 @@ foreach dep2 : ['sh', 'st']
main_c = configure_file(input : 'main.c.in',
output : name + '-main.c',
configuration : cdata)
dep3_bin = executable(name, main_c, link_with : dep3_lib,
dep3_bin = executable(name + '_test', main_c, link_with : dep3_lib,
c_args : build_args)
test(name + 'test', dep3_bin)
endforeach

Loading…
Cancel
Save