From 77d163a0e9eb9717a13a9c1a8839df9d48b9fc84 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 16 Feb 2020 01:38:28 +0530 Subject: symbolextractor: Print one warning when no implementation found So people know why all their binaries are getting relinked. Do this only once per build-dir by writing a file to meson-private. --- mesonbuild/backend/ninjabackend.py | 1 + mesonbuild/scripts/symbolextractor.py | 26 +++++++++++++++++++------- 2 files changed, 20 insertions(+), 7 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index bcb916c..bccc445 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1584,6 +1584,7 @@ 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', '$out'] symrule = 'SHSYM' diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index 410cb33..1bce6e4 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -22,6 +22,7 @@ import os, sys from .. import mesonlib +from .. import mlog from ..mesonlib import Popen_safe import argparse @@ -31,6 +32,9 @@ parser.add_argument('--cross-host', default=None, dest='cross_host', help='cross compilation host platform') parser.add_argument('args', nargs='+') +TOOL_WARNING_FILE = None +RELINKING_WARNING = 'Relinking will always happen on source changes.' + def dummy_syms(outfilename): """Just touch it so relinking happens always.""" with open(outfilename, 'w'): @@ -96,25 +100,33 @@ def osx_syms(libfilename, outfilename): def gen_symbols(libfilename, outfilename, cross_host): 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) 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: + if len(options.args) != 3: print('symbolextractor.py ') sys.exit(1) - libfile = options.args[0] - outfile = options.args[1] + privdir = os.path.join(options.args[0], 'meson-private') + TOOL_WARNING_FILE = os.path.join(privdir, 'symbolextractor_tool_warning_printed') + libfile = options.args[1] + outfile = options.args[2] gen_symbols(libfile, outfile, options.cross_host) return 0 -- cgit v1.1 From 431283b35dff5b43ecc65fe19ae581b3c6d8b9be Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 16 Feb 2020 02:50:15 +0530 Subject: symbolextractor: Correctly filter undefined symbols on macOS -g is --extern-only and -P is --format=posix. We were missing --defined-only for some reason, which we pass to `nm` on Linux. This avoids having to manually filter later. --- mesonbuild/scripts/symbolextractor.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index 1bce6e4..e889f62 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -92,10 +92,12 @@ def osx_syms(libfilename, outfilename): 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] + pnm, output = Popen_safe(['nm', '--extern-only', + '--defined-only', '--format=posix', + 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')] + result += [' '.join(x.split()[0:2]) for x in output.split('\n')] write_if_changed('\n'.join(result) + '\n', outfilename) def gen_symbols(libfilename, outfilename, cross_host): -- cgit v1.1 From 6fe7af58091f0cb9c7a4c41e6f714d5a8e9c9ad7 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 16 Feb 2020 02:52:58 +0530 Subject: symbolextractor: Print a warning if required tools not found Also write out a dummy symbols file if the tool wasn't found or didn't work instead of just spewing an exception. --- mesonbuild/scripts/symbolextractor.py | 75 +++++++++++++++++++++++------------ 1 file changed, 49 insertions(+), 26 deletions(-) diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index e889f62..dea7284 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -51,27 +51,47 @@ 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, msg, stderr=None): + global TOOL_WARNING_FILE + if os.path.exists(TOOL_WARNING_FILE): + return + 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 call_tool(name, args): + 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') + name = os.environ[evar].strip() + # Run it + try: + p, output, e = Popen_safe([name] + args) + except FileNotFoundError: + print_tool_warning(tool, 'not found') + return None + if p.returncode != 0: + print_tool_warning(name, 'does not work', e) + return None + return output + +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 @@ -83,20 +103,23 @@ def linux_syms(libfilename, outfilename): 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.') + # 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', '--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', '--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) -- cgit v1.1 From 901bbc36d902c8146482295827e5464070bbf898 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 16 Feb 2020 03:05:05 +0530 Subject: symbolextractor: Support passing arguments to tools This is how we parse all env vars for tools in Meson. Do the same here too for consistency. --- mesonbuild/scripts/symbolextractor.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index dea7284..b168acc 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -55,6 +55,8 @@ def print_tool_warning(tool, msg, stderr=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 @@ -66,10 +68,13 @@ def print_tool_warning(tool, msg, stderr=None): def call_tool(name, args): evar = name.upper() if evar in os.environ: - name = os.environ[evar].strip() + import shlex + name = shlex.split(os.environ[evar]) + else: + name = [name] # Run it try: - p, output, e = Popen_safe([name] + args) + p, output, e = Popen_safe(name + args) except FileNotFoundError: print_tool_warning(tool, 'not found') return None @@ -80,15 +85,15 @@ def call_tool(name, args): def linux_syms(libfilename: str, outfilename: str): # Get the name of the library - output = call_tool(['readelf', '-d', libfilename]) + 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) # Get a list of all symbols exported - output = call_tool(['nm', '--dynamic', '--extern-only', '--defined-only', - '--format=posix', libfilename]) + output = call_tool('nm', ['--dynamic', '--extern-only', '--defined-only', + '--format=posix', libfilename]) if not output: dummy_syms(outfilename) return @@ -104,7 +109,7 @@ def linux_syms(libfilename: str, outfilename: str): def osx_syms(libfilename, outfilename): # Get the name of the library - output = call_tool(['otool', '-l', libfilename]) + output = call_tool('otool', ['-l', libfilename]) if not output: dummy_syms(outfilename) return @@ -115,8 +120,8 @@ def osx_syms(libfilename, outfilename): break result = [arr[match + 2], arr[match + 5]] # Libreoffice stores all 5 lines but the others seem irrelevant. # Get a list of all symbols exported - output = call_tool(['nm', '--extern-only', '--defined-only', - '--format=posix', libfilename]) + output = call_tool('nm', ['--extern-only', '--defined-only', + '--format=posix', libfilename]) if not output: dummy_syms(outfilename) return -- cgit v1.1 From feb82e0f0f365d66199d697f1d99e109e2c6a700 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 16 Feb 2020 19:25:07 +0530 Subject: symbolextractor: Add typing hints --- mesonbuild/scripts/symbolextractor.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index b168acc..f94595f 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -20,6 +20,7 @@ # 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 @@ -35,12 +36,12 @@ parser.add_argument('args', nargs='+') TOOL_WARNING_FILE = None RELINKING_WARNING = 'Relinking will always happen on source changes.' -def dummy_syms(outfilename): +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() @@ -51,7 +52,7 @@ def write_if_changed(text, outfilename): with open(outfilename, 'w') as f: f.write(text) -def print_tool_warning(tool, msg, stderr=None): +def print_tool_warning(tool: list, msg: str, stderr: str = None): global TOOL_WARNING_FILE if os.path.exists(TOOL_WARNING_FILE): return @@ -65,7 +66,7 @@ def print_tool_warning(tool, msg, stderr=None): with open(TOOL_WARNING_FILE, 'w'): pass -def call_tool(name, args): +def call_tool(name: str, args: T.List[str]) -> str: evar = name.upper() if evar in os.environ: import shlex @@ -107,7 +108,7 @@ def linux_syms(libfilename: str, outfilename: str): result += [' '.join(entry)] write_if_changed('\n'.join(result) + '\n', outfilename) -def osx_syms(libfilename, outfilename): +def osx_syms(libfilename: str, outfilename: str): # Get the name of the library output = call_tool('otool', ['-l', libfilename]) if not output: @@ -128,7 +129,7 @@ def osx_syms(libfilename, outfilename): result += [' '.join(x.split()[0:2]) for x in output.split('\n')] write_if_changed('\n'.join(result) + '\n', outfilename) -def gen_symbols(libfilename, outfilename, cross_host): +def gen_symbols(libfilename: 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 we would need to use the correct -- cgit v1.1 From be486a2ec84f22052fba5ba16de136de00379966 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 16 Feb 2020 18:31:32 +0530 Subject: ninjabackend: List PDBs in output list for targets This is more correct, and forces the target(s) to be rebuilt if the PDB files are missing. Increases the minimum required Ninja to 1.7, which is available in Ubuntu 16.04 under backports. We can't do the same for import libraries, because it is impossible for us to know at configure time whether or not an import library will be generated for a given DLL. --- docs/markdown/snippets/ninja_version_bump.md | 10 ++++++++++ mesonbuild/backend/ninjabackend.py | 21 ++++++++++++++++----- mesonbuild/compilers/compilers.py | 3 +++ mesonbuild/environment.py | 4 ++-- mesonbuild/linkers.py | 15 ++++++++++++--- run_tests.py | 18 ++++++++---------- 6 files changed, 51 insertions(+), 20 deletions(-) create mode 100644 docs/markdown/snippets/ninja_version_bump.md diff --git a/docs/markdown/snippets/ninja_version_bump.md b/docs/markdown/snippets/ninja_version_bump.md new file mode 100644 index 0000000..9c5f6e8 --- /dev/null +++ b/docs/markdown/snippets/ninja_version_bump.md @@ -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§ion=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 diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index bccc445..11fd92f 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -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: @@ -1947,6 +1951,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) @@ -2449,6 +2456,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: @@ -2484,6 +2492,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) @@ -2572,7 +2583,7 @@ 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 diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py index e8e72cf..67686a9 100644 --- a/mesonbuild/compilers/compilers.py +++ b/mesonbuild/compilers/compilers.py @@ -976,6 +976,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) diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py index 554d79b..a928248 100644 --- a/mesonbuild/environment.py +++ b/mesonbuild/environment.py @@ -140,11 +140,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: diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py index 9781813..b1d80c3 100644 --- a/mesonbuild/linkers.py +++ b/mesonbuild/linkers.py @@ -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. @@ -812,10 +819,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 diff --git a/run_tests.py b/run_tests.py index 535c792..3237e85 100755 --- a/run_tests.py +++ b/run_tests.py @@ -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 -- cgit v1.1 From 7f1d78f30479170baa749e179c70e85a62250b0f Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 17 Feb 2020 00:42:08 +0530 Subject: tests: Ensure that executable and library are named differently On Windows, the basename is used to determine the name of the PDB file. So for a project called myproject, we will create myproject.dll and myproject.exe, both of which will have myproject.pdb. This is a file collision. Instead, append `_test`, similar to the C# template. Fixes AllPlatformTest.test_templates on MSVC. This became a hard error when we started listing PDBs in the implicit outputs list of ninja targets. Do the same for a test that was making the same mistake. --- mesonbuild/templates/cpptemplates.py | 3 ++- mesonbuild/templates/ctemplates.py | 3 ++- mesonbuild/templates/cudatemplates.py | 3 ++- mesonbuild/templates/dlangtemplates.py | 3 ++- mesonbuild/templates/fortrantemplates.py | 3 ++- mesonbuild/templates/javatemplates.py | 1 - mesonbuild/templates/objcpptemplates.py | 3 ++- mesonbuild/templates/objctemplates.py | 3 ++- mesonbuild/templates/rusttemplates.py | 3 ++- test cases/common/149 recursive linking/3rdorderdeps/meson.build | 2 +- 10 files changed, 17 insertions(+), 10 deletions(-) diff --git a/mesonbuild/templates/cpptemplates.py b/mesonbuild/templates/cpptemplates.py index 5bff67b..f664e42 100644 --- a/mesonbuild/templates/cpptemplates.py +++ b/mesonbuild/templates/cpptemplates.py @@ -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, diff --git a/mesonbuild/templates/ctemplates.py b/mesonbuild/templates/ctemplates.py index f46f054..64686c8 100644 --- a/mesonbuild/templates/ctemplates.py +++ b/mesonbuild/templates/ctemplates.py @@ -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, diff --git a/mesonbuild/templates/cudatemplates.py b/mesonbuild/templates/cudatemplates.py index d083fe8..cc0782c 100644 --- a/mesonbuild/templates/cudatemplates.py +++ b/mesonbuild/templates/cudatemplates.py @@ -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, diff --git a/mesonbuild/templates/dlangtemplates.py b/mesonbuild/templates/dlangtemplates.py index 124634c..265e3d5 100644 --- a/mesonbuild/templates/dlangtemplates.py +++ b/mesonbuild/templates/dlangtemplates.py @@ -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, diff --git a/mesonbuild/templates/fortrantemplates.py b/mesonbuild/templates/fortrantemplates.py index 3bf1b74..b784fda 100644 --- a/mesonbuild/templates/fortrantemplates.py +++ b/mesonbuild/templates/fortrantemplates.py @@ -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, diff --git a/mesonbuild/templates/javatemplates.py b/mesonbuild/templates/javatemplates.py index e8a8c15..012823a 100644 --- a/mesonbuild/templates/javatemplates.py +++ b/mesonbuild/templates/javatemplates.py @@ -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, diff --git a/mesonbuild/templates/objcpptemplates.py b/mesonbuild/templates/objcpptemplates.py index 329a568..2d71573 100644 --- a/mesonbuild/templates/objcpptemplates.py +++ b/mesonbuild/templates/objcpptemplates.py @@ -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, diff --git a/mesonbuild/templates/objctemplates.py b/mesonbuild/templates/objctemplates.py index db89c28..73791f5 100644 --- a/mesonbuild/templates/objctemplates.py +++ b/mesonbuild/templates/objctemplates.py @@ -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, diff --git a/mesonbuild/templates/rusttemplates.py b/mesonbuild/templates/rusttemplates.py index 848dfc0..ab8ecbd 100644 --- a/mesonbuild/templates/rusttemplates.py +++ b/mesonbuild/templates/rusttemplates.py @@ -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, diff --git a/test cases/common/149 recursive linking/3rdorderdeps/meson.build b/test cases/common/149 recursive linking/3rdorderdeps/meson.build index d4ef745..4c5ac73 100644 --- a/test cases/common/149 recursive linking/3rdorderdeps/meson.build +++ b/test cases/common/149 recursive linking/3rdorderdeps/meson.build @@ -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 -- cgit v1.1 From 72c6cbd990c7f27fab609a1922d6f4b8f5ecdac3 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 16 Feb 2020 21:01:21 +0530 Subject: ninjabackend: Minor refactoring --- mesonbuild/backend/ninjabackend.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 11fd92f..11db192 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -2326,6 +2326,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): @@ -2336,7 +2339,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): @@ -2357,7 +2360,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: -- cgit v1.1 From 5dcbf10a1bdc6980622be90d52322a7d287070dc Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 16 Feb 2020 21:20:35 +0530 Subject: ninjabackend: Pass the import library to SHSYM We actually use this while linking on Windows, and hence we need to extract symbols from this file, and not the DLL. However, we cannot pass it instead of the DLL because it's an optional output of the compiler. It will not be written out at all if there are no symbols in the DLL, and we cannot know that at configure time. This means we cannot describe it as an output of any ninja target, or the input of any ninja target. We must pass it as an argument without semantic meaning. --- mesonbuild/backend/ninjabackend.py | 14 ++++++++++---- mesonbuild/scripts/symbolextractor.py | 11 ++++++----- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index 11db192..d6387d5 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -1590,6 +1590,7 @@ int dummy; 'symbolextractor', ninja_quote(quote_func(self.environment.get_build_dir())), '$in', + '$IMPLIB', '$out'] symrule = 'SHSYM' symcmd = args + ['$CROSS'] @@ -2308,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) @@ -2593,7 +2599,7 @@ https://gcc.gnu.org/bugzilla/show_bug.cgi?id=47485''')) 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() diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index f94595f..fd42dde 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -129,7 +129,7 @@ def osx_syms(libfilename: str, outfilename: str): result += [' '.join(x.split()[0:2]) for x in output.split('\n')] write_if_changed('\n'.join(result) + '\n', outfilename) -def gen_symbols(libfilename: str, outfilename: str, cross_host: str): +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 we would need to use the correct @@ -151,14 +151,15 @@ def gen_symbols(libfilename: str, outfilename: str, cross_host: str): def run(args): global TOOL_WARNING_FILE options = parser.parse_args(args) - if len(options.args) != 3: - print('symbolextractor.py ') + if len(options.args) != 4: + print('symbolextractor.py ') sys.exit(1) privdir = os.path.join(options.args[0], 'meson-private') TOOL_WARNING_FILE = os.path.join(privdir, 'symbolextractor_tool_warning_printed') libfile = options.args[1] - outfile = options.args[2] - gen_symbols(libfile, outfile, options.cross_host) + 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__': -- cgit v1.1 From 26615ac422e0591e23624d124488fb82cc472875 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 16 Feb 2020 21:53:57 +0530 Subject: unit tests: Skip if pkg-config is not found Of course, this does not skip on the CI, but helps on Windows. --- run_unittests.py | 1 + 1 file changed, 1 insertion(+) diff --git a/run_unittests.py b/run_unittests.py index 7c2ae05..f26ab7e 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -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 -- cgit v1.1 From 225d842e4c5aa5159c4e706f071dba4a9fdb0958 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 16 Feb 2020 21:57:53 +0530 Subject: unit tests: Make assertBuildNoOp check stricter We also need to verify that no CustomBuild targets were rebuilt. --- run_unittests.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/run_unittests.py b/run_unittests.py index f26ab7e..7a22903 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1706,15 +1706,15 @@ class BasePlatformTests(unittest.TestCase): 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: -- cgit v1.1 From b0061257c93da955397da05fb0783801f67cd54d Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Sun, 16 Feb 2020 21:58:58 +0530 Subject: unit tests: Add a test for reconfigure causing no-op build meson setup && ninja && touch meson.build && ninja should only reconfigure but not cause anything to be rebuilt. --- run_unittests.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/run_unittests.py b/run_unittests.py index 7a22903..500b5eb 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1701,6 +1701,29 @@ 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: @@ -2536,6 +2559,20 @@ 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() + def test_source_changes_cause_rebuild(self): ''' Test that changes to sources and headers cause rebuilds, but not -- cgit v1.1 From 3320e13d91d68f0a4898901c168b3981b76dec41 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 17 Feb 2020 00:08:37 +0530 Subject: unit tests: Add a test for the symbolchecker script When a source file for a library is changed without adding new extern symbols, only that library should be rebuilt. Nothing that uses it should be relinked. Along the way, also remove trailing `.` in all Ninja rule descriptions. It's very confusing to see messages like: ``` Linking target mylib.dll. ``` It's confusing that the period at the end of that is not part of the filename. Instead of removing that period manually in the tests (which feels wrong!) just don't print it at all. --- mesonbuild/backend/ninjabackend.py | 46 +++++++++++++++++++------------------- run_unittests.py | 34 ++++++++++++++++++++++++++-- 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py index d6387d5..5f5de0a 100644 --- a/mesonbuild/backend/ninjabackend.py +++ b/mesonbuild/backend/ninjabackend.py @@ -668,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: @@ -764,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) @@ -789,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') @@ -798,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') @@ -979,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 @@ -1073,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): @@ -1554,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: @@ -1576,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: @@ -1594,7 +1594,7 @@ int dummy; '$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)) @@ -1602,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): @@ -1610,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())) @@ -1618,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, @@ -1640,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): @@ -1660,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 @@ -1697,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 @@ -1724,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 @@ -2628,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') @@ -2642,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') @@ -2651,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') @@ -2762,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') diff --git a/run_unittests.py b/run_unittests.py index 500b5eb..80c7cd0 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1756,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'): + 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) @@ -2572,6 +2599,9 @@ class AllPlatformTests(BasePlatformTests): # 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): ''' @@ -2586,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): ''' @@ -2602,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): ''' -- cgit v1.1 From cace70c64eab4c4f5ba5bca7e76e02cab275b025 Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Mon, 17 Feb 2020 00:18:36 +0530 Subject: symbolextractor: Add a Windows implementation Supports both MSVC and MinGW toolchains. Checks for MSVC first, then falls back to MinGW. --- mesonbuild/scripts/symbolextractor.py | 87 ++++++++++++++++++++++++++++++++--- run_unittests.py | 2 + 2 files changed, 82 insertions(+), 7 deletions(-) diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index fd42dde..2172243 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -66,24 +66,34 @@ def print_tool_warning(tool: list, msg: str, stderr: str = None): with open(TOOL_WARNING_FILE, 'w'): pass -def call_tool(name: str, args: T.List[str]) -> str: +def get_tool(name: str) -> T.List[str]: evar = name.upper() if evar in os.environ: import shlex - name = shlex.split(os.environ[evar]) - else: - name = [name] - # Run it + 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(name + args) + 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(name, 'does not work', e) + 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]) @@ -129,6 +139,62 @@ def osx_syms(libfilename: str, outfilename: str): result += [' '.join(x.split()[0:2]) for x in output.split('\n')] write_if_changed('\n'.join(result) + '\n', outfilename) +def _get_implib_dllname(impfilename: str) -> T.Tuple[T.List[str], str]: + # First try lib.exe, which is provided by MSVC. + # Do not allow overriding by setting the LIB env var, because that is + # already used for something else: it's the list of library paths MSVC + # will search for import libraries while linking. + output, e1 = 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 + # Next, try dlltool.exe which is provided by MinGW + output, e2 = call_tool_nowarn(get_tool('dlltool') + ['-I', impfilename]) + if output: + return [output], None + return ([], (e1 + e2)) + +def _get_implib_exports(impfilename: str) -> T.Tuple[T.List[str], str]: + # Force dumpbin.exe to use en-US so we can parse its output + env = os.environ.copy() + env['VSLANG'] = '1033' + output, e1 = 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 + # Next, try nm.exe which is provided by MinGW + output, e2 = 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 + return ([], (e1 + e2)) + +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('Both lib.exe and dlltool.exe', 'do not work', e) + dummy_syms(outfilename) + return + # Get a list of all symbols exported + symbols, e = _get_implib_exports(impfilename) + if not symbols: + print_tool_warning('Both dumpbin.exe and nm.exe', 'do not work', e) + dummy_syms(outfilename) + return + result += symbols + write_if_changed('\n'.join(result) + '\n', outfilename) + 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 @@ -139,6 +205,13 @@ def gen_symbols(libfilename: str, impfilename: str, outfilename: str, cross_host 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) else: if not os.path.exists(TOOL_WARNING_FILE): mlog.warning('Symbol extracting has not been implemented for this ' diff --git a/run_unittests.py b/run_unittests.py index 80c7cd0..51193f0 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -2591,6 +2591,8 @@ class AllPlatformTests(BasePlatformTests): Test that no-op changes to the build files such as mtime do not cause a rebuild of anything. ''' + if is_cygwin(): + raise unittest.SkipTest('symbolextractor has not been implemented for Cygwin yet') testdir = os.path.join(self.common_test_dir, '6 linkshared') self.init(testdir) self.build() -- cgit v1.1 From cbd143844d54694fda90330e0d49c7ac408c532e Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 18 Feb 2020 01:12:34 +0530 Subject: symbolextractor: Add support for clang-cl Requires the latest LLVm 9.0 release which implements the `-list` argument to `llvm-lib` and ships with an implementation of `nm` called `llvm-nm`. --- ci/azure-steps.yml | 4 +-- mesonbuild/scripts/symbolextractor.py | 64 ++++++++++++++++++++--------------- 2 files changed, 39 insertions(+), 29 deletions(-) diff --git a/ci/azure-steps.yml b/ci/azure-steps.yml index 1c861e7..15832bb 100644 --- a/ci/azure-steps.yml +++ b/ci/azure-steps.yml @@ -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" diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index 2172243..25416c6 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -140,56 +140,66 @@ def osx_syms(libfilename: str, outfilename: str): write_if_changed('\n'.join(result) + '\n', outfilename) def _get_implib_dllname(impfilename: str) -> T.Tuple[T.List[str], str]: - # First try lib.exe, which is provided by MSVC. - # Do not allow overriding by setting the LIB env var, because that is - # already used for something else: it's the list of library paths MSVC - # will search for import libraries while linking. - output, e1 = 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 = '' + # 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, e2 = call_tool_nowarn(get_tool('dlltool') + ['-I', impfilename]) + output, e = call_tool_nowarn(get_tool('dlltool') + ['-I', impfilename]) if output: return [output], None - return ([], (e1 + e2)) + 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, e1 = call_tool_nowarn(get_tool('dumpbin') + ['-exports', impfilename], env=env) + 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 - # Next, try nm.exe which is provided by MinGW - output, e2 = 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 - return ([], (e1 + e2)) + 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('Both lib.exe and dlltool.exe', 'do not work', e) + 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('Both dumpbin.exe and nm.exe', 'do not work', e) + print_tool_warning('dumpbin, llvm-nm, nm', 'do not work or were not found', e) dummy_syms(outfilename) return result += symbols -- cgit v1.1 From 04e89d0867e358df347dbc8cb91e6df7bc365d9a Mon Sep 17 00:00:00 2001 From: Nirbheek Chauhan Date: Tue, 18 Feb 2020 02:08:45 +0530 Subject: symbolextractor: Add support for Cygwin --- mesonbuild/scripts/symbolextractor.py | 26 ++++++++++++++++++++++++++ run_unittests.py | 4 +--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py index 25416c6..65b2189 100644 --- a/mesonbuild/scripts/symbolextractor.py +++ b/mesonbuild/scripts/symbolextractor.py @@ -139,6 +139,25 @@ def osx_syms(libfilename: str, outfilename: str): 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 @@ -222,6 +241,13 @@ def gen_symbols(libfilename: str, impfilename: str, outfilename: str, cross_host # 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 ' diff --git a/run_unittests.py b/run_unittests.py index 51193f0..af2ca9d 100755 --- a/run_unittests.py +++ b/run_unittests.py @@ -1759,7 +1759,7 @@ class BasePlatformTests(unittest.TestCase): @staticmethod def get_target_from_filename(filename): base = os.path.splitext(filename)[0] - if base.startswith('lib'): + if base.startswith(('lib', 'cyg')): return base[3:] return base @@ -2591,8 +2591,6 @@ class AllPlatformTests(BasePlatformTests): Test that no-op changes to the build files such as mtime do not cause a rebuild of anything. ''' - if is_cygwin(): - raise unittest.SkipTest('symbolextractor has not been implemented for Cygwin yet') testdir = os.path.join(self.common_test_dir, '6 linkshared') self.init(testdir) self.build() -- cgit v1.1