aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2020-02-23 20:23:03 +0200
committerGitHub <noreply@github.com>2020-02-23 20:23:03 +0200
commit9c604320a0cd47a5b22b52e157d43b73b0533e82 (patch)
tree0e90134be40901fcbea20faf36c539b474b80683
parentee94cb6c15732c8b5f329164af7f2b0a63f4fa6d (diff)
parent04e89d0867e358df347dbc8cb91e6df7bc365d9a (diff)
downloadmeson-9c604320a0cd47a5b22b52e157d43b73b0533e82.zip
meson-9c604320a0cd47a5b22b52e157d43b73b0533e82.tar.gz
meson-9c604320a0cd47a5b22b52e157d43b73b0533e82.tar.bz2
Merge pull request #6637 from mesonbuild/nirbheek/implement-symbolextractor-windows
Implement symbolextractor on windows + some cleanups/fixes
-rw-r--r--ci/azure-steps.yml4
-rw-r--r--docs/markdown/snippets/ninja_version_bump.md10
-rw-r--r--mesonbuild/backend/ninjabackend.py89
-rw-r--r--mesonbuild/compilers/compilers.py3
-rw-r--r--mesonbuild/environment.py4
-rw-r--r--mesonbuild/linkers.py15
-rw-r--r--mesonbuild/scripts/symbolextractor.py229
-rw-r--r--mesonbuild/templates/cpptemplates.py3
-rw-r--r--mesonbuild/templates/ctemplates.py3
-rw-r--r--mesonbuild/templates/cudatemplates.py3
-rw-r--r--mesonbuild/templates/dlangtemplates.py3
-rw-r--r--mesonbuild/templates/fortrantemplates.py3
-rw-r--r--mesonbuild/templates/javatemplates.py1
-rw-r--r--mesonbuild/templates/objcpptemplates.py3
-rw-r--r--mesonbuild/templates/objctemplates.py3
-rw-r--r--mesonbuild/templates/rusttemplates.py3
-rwxr-xr-xrun_tests.py18
-rwxr-xr-xrun_unittests.py82
-rw-r--r--test cases/common/149 recursive linking/3rdorderdeps/meson.build2
19 files changed, 375 insertions, 106 deletions
diff --git a/ci/azure-steps.yml b/ci/azure-steps.yml
index fa28046..8ec219e 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/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&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
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index bcb916c..5f5de0a 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:
@@ -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')
diff --git a/mesonbuild/compilers/compilers.py b/mesonbuild/compilers/compilers.py
index 2f1e104..37cf2e0 100644
--- a/mesonbuild/compilers/compilers.py
+++ b/mesonbuild/compilers/compilers.py
@@ -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)
diff --git a/mesonbuild/environment.py b/mesonbuild/environment.py
index eb626b0..46bbea0 100644
--- a/mesonbuild/environment.py
+++ b/mesonbuild/environment.py
@@ -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:
diff --git a/mesonbuild/linkers.py b/mesonbuild/linkers.py
index 84eb359..489525b 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.
@@ -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
diff --git a/mesonbuild/scripts/symbolextractor.py b/mesonbuild/scripts/symbolextractor.py
index 410cb33..65b2189 100644
--- a/mesonbuild/scripts/symbolextractor.py
+++ b/mesonbuild/scripts/symbolextractor.py
@@ -20,8 +20,10 @@
# This file is basically a reimplementation of
# http://cgit.freedesktop.org/libreoffice/core/commit/?id=3213cd54b76bc80a6f0516aac75a48ff3b2ad67c
+import typing as T
import os, sys
from .. import mesonlib
+from .. import mlog
from ..mesonlib import Popen_safe
import argparse
@@ -31,12 +33,15 @@ parser.add_argument('--cross-host', default=None, dest='cross_host',
help='cross compilation host platform')
parser.add_argument('args', nargs='+')
-def dummy_syms(outfilename):
+TOOL_WARNING_FILE = None
+RELINKING_WARNING = 'Relinking will always happen on source changes.'
+
+def dummy_syms(outfilename: str):
"""Just touch it so relinking happens always."""
with open(outfilename, 'w'):
pass
-def write_if_changed(text, outfilename):
+def write_if_changed(text: str, outfilename: str):
try:
with open(outfilename, 'r') as f:
oldtext = f.read()
@@ -47,27 +52,62 @@ def write_if_changed(text, outfilename):
with open(outfilename, 'w') as f:
f.write(text)
-def linux_syms(libfilename, outfilename):
- evar = 'READELF'
- if evar in os.environ:
- readelfbin = os.environ[evar].strip()
- else:
- readelfbin = 'readelf'
- evar = 'NM'
+def print_tool_warning(tool: list, msg: str, stderr: str = None):
+ global TOOL_WARNING_FILE
+ if os.path.exists(TOOL_WARNING_FILE):
+ return
+ if len(tool) == 1:
+ tool = tool[0]
+ m = '{!r} {}. {}'.format(tool, msg, RELINKING_WARNING)
+ if stderr:
+ m += '\n' + stderr
+ mlog.warning(m)
+ # Write it out so we don't warn again
+ with open(TOOL_WARNING_FILE, 'w'):
+ pass
+
+def get_tool(name: str) -> T.List[str]:
+ evar = name.upper()
if evar in os.environ:
- nmbin = os.environ[evar].strip()
- else:
- nmbin = 'nm'
- pe, output = Popen_safe([readelfbin, '-d', libfilename])[0:2]
- if pe.returncode != 0:
- raise RuntimeError('Readelf does not work')
+ import shlex
+ return shlex.split(os.environ[evar])
+ return [name]
+
+def call_tool(name: str, args: T.List[str], **kwargs) -> str:
+ tool = get_tool(name)
+ try:
+ p, output, e = Popen_safe(tool + args, **kwargs)
+ except FileNotFoundError:
+ print_tool_warning(tool, 'not found')
+ return None
+ if p.returncode != 0:
+ print_tool_warning(tool, 'does not work', e)
+ return None
+ return output
+
+def call_tool_nowarn(tool: T.List[str], **kwargs) -> T.Tuple[str, str]:
+ try:
+ p, output, e = Popen_safe(tool, **kwargs)
+ except FileNotFoundError:
+ return None, '{!r} not found\n'.format(tool[0])
+ if p.returncode != 0:
+ return None, e
+ return output, None
+
+def linux_syms(libfilename: str, outfilename: str):
+ # Get the name of the library
+ output = call_tool('readelf', ['-d', libfilename])
+ if not output:
+ dummy_syms(outfilename)
+ return
result = [x for x in output.split('\n') if 'SONAME' in x]
assert(len(result) <= 1)
- pnm, output = Popen_safe([nmbin, '--dynamic', '--extern-only',
- '--defined-only', '--format=posix',
- libfilename])[0:2]
- if pnm.returncode != 0:
- raise RuntimeError('nm does not work.')
+ # Get a list of all symbols exported
+ output = call_tool('nm', ['--dynamic', '--extern-only', '--defined-only',
+ '--format=posix', libfilename])
+ if not output:
+ dummy_syms(outfilename)
+ return
for line in output.split('\n'):
if not line:
continue
@@ -78,44 +118,157 @@ def linux_syms(libfilename, outfilename):
result += [' '.join(entry)]
write_if_changed('\n'.join(result) + '\n', outfilename)
-def osx_syms(libfilename, outfilename):
- pe, output = Popen_safe(['otool', '-l', libfilename])[0:2]
- if pe.returncode != 0:
- raise RuntimeError('Otool does not work.')
+def osx_syms(libfilename: str, outfilename: str):
+ # Get the name of the library
+ output = call_tool('otool', ['-l', libfilename])
+ if not output:
+ dummy_syms(outfilename)
+ return
arr = output.split('\n')
for (i, val) in enumerate(arr):
if 'LC_ID_DYLIB' in val:
match = i
break
result = [arr[match + 2], arr[match + 5]] # Libreoffice stores all 5 lines but the others seem irrelevant.
- pnm, output = Popen_safe(['nm', '-g', '-P', libfilename])[0:2]
- if pnm.returncode != 0:
- raise RuntimeError('nm does not work.')
- result += [' '.join(x.split()[0:2]) for x in output.split('\n') if x and not x.endswith('U')]
+ # Get a list of all symbols exported
+ output = call_tool('nm', ['--extern-only', '--defined-only',
+ '--format=posix', libfilename])
+ if not output:
+ dummy_syms(outfilename)
+ return
+ result += [' '.join(x.split()[0:2]) for x in output.split('\n')]
+ write_if_changed('\n'.join(result) + '\n', outfilename)
+
+def cygwin_syms(impfilename: str, outfilename: str):
+ # Get the name of the library
+ output = call_tool('dlltool', ['-I', impfilename])
+ if not output:
+ dummy_syms(outfilename)
+ return
+ result = [output]
+ # Get the list of all symbols exported
+ output = call_tool('nm', ['--extern-only', '--defined-only',
+ '--format=posix', impfilename])
+ if not output:
+ dummy_syms(outfilename)
+ return
+ for line in output.split('\n'):
+ if ' T ' not in line:
+ continue
+ result.append(line.split(maxsplit=1)[0])
+ write_if_changed('\n'.join(result) + '\n', outfilename)
+
+def _get_implib_dllname(impfilename: str) -> T.Tuple[T.List[str], str]:
+ all_stderr = ''
+ # First try lib.exe, which is provided by MSVC. Then llvm-lib.exe, by LLVM
+ # for clang-cl.
+ #
+ # We cannot call get_tool on `lib` because it will look at the `LIB` env
+ # var which is the list of library paths MSVC will search for import
+ # libraries while linking.
+ for lib in (['lib'], get_tool('llvm-lib')):
+ output, e = call_tool_nowarn(lib + ['-list', impfilename])
+ if output:
+ # The output is a list of DLLs that each symbol exported by the import
+ # library is available in. We only build import libraries that point to
+ # a single DLL, so we can pick any of these. Pick the last one for
+ # simplicity. Also skip the last line, which is empty.
+ return output.split('\n')[-2:-1], None
+ all_stderr += e
+ # Next, try dlltool.exe which is provided by MinGW
+ output, e = call_tool_nowarn(get_tool('dlltool') + ['-I', impfilename])
+ if output:
+ return [output], None
+ all_stderr += e
+ return ([], all_stderr)
+
+def _get_implib_exports(impfilename: str) -> T.Tuple[T.List[str], str]:
+ all_stderr = ''
+ # Force dumpbin.exe to use en-US so we can parse its output
+ env = os.environ.copy()
+ env['VSLANG'] = '1033'
+ output, e = call_tool_nowarn(get_tool('dumpbin') + ['-exports', impfilename], env=env)
+ if output:
+ lines = output.split('\n')
+ start = lines.index('File Type: LIBRARY')
+ end = lines.index(' Summary')
+ return lines[start:end], None
+ all_stderr += e
+ # Next, try llvm-nm.exe provided by LLVM, then nm.exe provided by MinGW
+ for nm in ('llvm-nm', 'nm'):
+ output, e = call_tool_nowarn(get_tool(nm) + ['--extern-only', '--defined-only',
+ '--format=posix', impfilename])
+ if output:
+ result = []
+ for line in output.split('\n'):
+ if ' T ' not in line or line.startswith('.text'):
+ continue
+ result.append(line.split(maxsplit=1)[0])
+ return result, None
+ all_stderr += e
+ return ([], all_stderr)
+
+def windows_syms(impfilename: str, outfilename: str):
+ # Get the name of the library
+ result, e = _get_implib_dllname(impfilename)
+ if not result:
+ print_tool_warning('lib, llvm-lib, dlltool', 'do not work or were not found', e)
+ dummy_syms(outfilename)
+ return
+ # Get a list of all symbols exported
+ symbols, e = _get_implib_exports(impfilename)
+ if not symbols:
+ print_tool_warning('dumpbin, llvm-nm, nm', 'do not work or were not found', e)
+ dummy_syms(outfilename)
+ return
+ result += symbols
write_if_changed('\n'.join(result) + '\n', outfilename)
-def gen_symbols(libfilename, outfilename, cross_host):
+def gen_symbols(libfilename: str, impfilename: str, outfilename: str, cross_host: str):
if cross_host is not None:
- # In case of cross builds just always relink.
- # In theory we could determine the correct
- # toolset but there are more important things
- # to do.
+ # In case of cross builds just always relink. In theory we could
+ # determine the correct toolset, but we would need to use the correct
+ # `nm`, `readelf`, etc, from the cross info which requires refactoring.
dummy_syms(outfilename)
elif mesonlib.is_linux():
linux_syms(libfilename, outfilename)
elif mesonlib.is_osx():
osx_syms(libfilename, outfilename)
+ elif mesonlib.is_windows():
+ if os.path.isfile(impfilename):
+ windows_syms(impfilename, outfilename)
+ else:
+ # No import library. Not sure how the DLL is being used, so just
+ # rebuild everything that links to it every time.
+ dummy_syms(outfilename)
+ elif mesonlib.is_cygwin():
+ if os.path.isfile(impfilename):
+ cygwin_syms(impfilename, outfilename)
+ else:
+ # No import library. Not sure how the DLL is being used, so just
+ # rebuild everything that links to it every time.
+ dummy_syms(outfilename)
else:
+ if not os.path.exists(TOOL_WARNING_FILE):
+ mlog.warning('Symbol extracting has not been implemented for this '
+ 'platform. ' + RELINKING_WARNING)
+ # Write it out so we don't warn again
+ with open(TOOL_WARNING_FILE, 'w'):
+ pass
dummy_syms(outfilename)
def run(args):
+ global TOOL_WARNING_FILE
options = parser.parse_args(args)
- if len(options.args) != 2:
- print('symbolextractor.py <shared library file> <output file>')
+ if len(options.args) != 4:
+ print('symbolextractor.py <shared library file> <import library> <output file>')
sys.exit(1)
- libfile = options.args[0]
- outfile = options.args[1]
- gen_symbols(libfile, outfile, options.cross_host)
+ privdir = os.path.join(options.args[0], 'meson-private')
+ TOOL_WARNING_FILE = os.path.join(privdir, 'symbolextractor_tool_warning_printed')
+ libfile = options.args[1]
+ impfile = options.args[2] # Only used on Windows
+ outfile = options.args[3]
+ gen_symbols(libfile, impfile, outfile, options.cross_host)
return 0
if __name__ == '__main__':
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/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
diff --git a/run_unittests.py b/run_unittests.py
index 033647c..aa27a1d 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
@@ -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):
'''
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