aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJussi Pakkanen <jpakkane@gmail.com>2017-04-11 22:58:23 +0300
committerGitHub <noreply@github.com>2017-04-11 22:58:23 +0300
commiteaaaee642132d3a568136dc740f74f8aaf4388b0 (patch)
treea749b2084b5444b34f6ae55fcc13ade3f77777fa
parent315a52533c077c898033ee1ab0d5f138d7dec905 (diff)
parent711c0cbd674a84fc2d28d0b92dfb62124180d3ef (diff)
downloadmeson-eaaaee642132d3a568136dc740f74f8aaf4388b0.zip
meson-eaaaee642132d3a568136dc740f74f8aaf4388b0.tar.gz
meson-eaaaee642132d3a568136dc740f74f8aaf4388b0.tar.bz2
Merge pull request #1596 from centricular/test-rebuilds
Test that build and custom targets are rebuilt on changes
-rw-r--r--mesonbuild/backend/backends.py16
-rw-r--r--mesonbuild/backend/ninjabackend.py6
-rw-r--r--mesonbuild/backend/vs2010backend.py39
-rw-r--r--mesonbuild/build.py6
-rwxr-xr-xrun_unittests.py90
-rw-r--r--test cases/common/22 header in file list/prog.c2
-rw-r--r--test cases/common/64 custom header generator/meson.build2
-rw-r--r--test cases/common/64 custom header generator/somefile.txt0
8 files changed, 124 insertions, 37 deletions
diff --git a/mesonbuild/backend/backends.py b/mesonbuild/backend/backends.py
index 2e630bd..e49793e 100644
--- a/mesonbuild/backend/backends.py
+++ b/mesonbuild/backend/backends.py
@@ -631,6 +631,22 @@ class Backend:
srcs += fname
return srcs
+ def get_custom_target_depend_files(self, target, absolute_paths=False):
+ deps = []
+ for i in target.depend_files:
+ if isinstance(i, mesonlib.File):
+ if absolute_paths:
+ deps.append(i.absolute_path(self.environment.get_source_dir(),
+ self.environment.get_build_dir()))
+ else:
+ deps.append(i.rel_to_builddir(self.build_to_src))
+ else:
+ if absolute_paths:
+ deps.append(os.path.join(self.environment.get_build_dir(), i))
+ else:
+ deps.append(os.path.join(self.build_to_src, i))
+ return deps
+
def eval_custom_target_command(self, target, absolute_outputs=False):
# We want the outputs to be absolute only when using the VS backend
# XXX: Maybe allow the vs backend to use relative paths too?
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index ec6d050..848aa59 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -468,6 +468,7 @@ int dummy;
self.custom_target_generator_inputs(target, outfile)
(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.'
if target.build_always:
deps.append('PHONY')
@@ -476,11 +477,6 @@ int dummy;
else:
rulename = 'CUSTOM_COMMAND_DEP'
elem = NinjaBuildElement(self.all_outputs, ofilenames, rulename, srcs)
- for i in target.depend_files:
- if isinstance(i, mesonlib.File):
- deps.append(i.rel_to_builddir(self.build_to_src))
- else:
- deps.append(os.path.join(self.build_to_src, i))
elem.add_dep(deps)
for d in target.extra_depends:
# Add a dependency on all the outputs of this target
diff --git a/mesonbuild/backend/vs2010backend.py b/mesonbuild/backend/vs2010backend.py
index feae79e..46eab11 100644
--- a/mesonbuild/backend/vs2010backend.py
+++ b/mesonbuild/backend/vs2010backend.py
@@ -427,8 +427,7 @@ class Vs2010Backend(backends.Backend):
ET.SubElement(customstep, 'Command').text = cmd_templ % tuple(cmd)
ET.SubElement(customstep, 'Message').text = 'Running custom command.'
ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets')
- tree = ET.ElementTree(root)
- tree.write(ofname, encoding='utf-8', xml_declaration=True)
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
def gen_custom_target_vcxproj(self, target, ofname, guid):
root = self.create_basic_crap(target)
@@ -438,6 +437,7 @@ class Vs2010Backend(backends.Backend):
# from the target dir, not the build root.
target.absolute_paths = True
(srcs, ofilenames, cmd) = self.eval_custom_target_command(target, True)
+ depend_files = self.get_custom_target_depend_files(target, True)
# Always use a wrapper because MSBuild eats random characters when
# there are many arguments.
tdir_abs = os.path.join(self.environment.get_build_dir(), self.get_target_dir(target))
@@ -449,11 +449,10 @@ class Vs2010Backend(backends.Backend):
'--internal', 'exe', exe_data]
ET.SubElement(customstep, 'Command').text = ' '.join(self.quote_arguments(wrapper_cmd))
ET.SubElement(customstep, 'Outputs').text = ';'.join(ofilenames)
- ET.SubElement(customstep, 'Inputs').text = ';'.join(srcs)
+ ET.SubElement(customstep, 'Inputs').text = ';'.join([exe_data] + srcs + depend_files)
ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets')
self.generate_custom_generator_commands(target, root)
- tree = ET.ElementTree(root)
- tree.write(ofname, encoding='utf-8', xml_declaration=True)
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
@classmethod
def lang_from_source_file(cls, src):
@@ -579,6 +578,13 @@ class Vs2010Backend(backends.Backend):
return c
raise MesonException('Could not find a C or C++ compiler. MSVC can only build C/C++ projects.')
+ def _prettyprint_vcxproj_xml(self, tree, ofname):
+ tree.write(ofname, encoding='utf-8', xml_declaration=True)
+ # ElementTree can not do prettyprinting so do it manually
+ doc = xml.dom.minidom.parse(ofname)
+ with open(ofname, 'w') as of:
+ of.write(doc.toprettyxml())
+
def gen_vcxproj(self, target, ofname, guid):
mlog.debug('Generating vcxproj %s.' % target.name)
entrypoint = 'WinMainCRTStartup'
@@ -1023,19 +1029,7 @@ class Vs2010Backend(backends.Backend):
ig = ET.SubElement(root, 'ItemGroup')
pref = ET.SubElement(ig, 'ProjectReference', Include=os.path.join(self.environment.get_build_dir(), 'REGEN.vcxproj'))
ET.SubElement(pref, 'Project').text = self.environment.coredata.regen_guid
- tree = ET.ElementTree(root)
- tree.write(ofname, encoding='utf-8', xml_declaration=True)
- # ElementTree can not do prettyprinting so do it manually
- doc = xml.dom.minidom.parse(ofname)
- with open(ofname, 'w') as of:
- of.write(doc.toprettyxml())
- # World of horror! Python insists on not quoting quotes and
- # fixing the escaped &quot; into &amp;quot; whereas MSVS
- # requires quoted but not fixed elements. Enter horrible hack.
- with open(ofname, 'r') as of:
- txt = of.read()
- with open(ofname, 'w') as of:
- of.write(txt.replace('&amp;quot;', '&quot;'))
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
def gen_regenproj(self, project_name, ofname):
root = ET.Element('Project', {'DefaultTargets': 'Build',
@@ -1114,8 +1108,7 @@ if %%errorlevel%% neq 0 goto :VCEnd'''
ET.SubElement(custombuild, 'AdditionalInputs').text = ';'.join(deps)
ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets')
ET.SubElement(root, 'ImportGroup', Label='ExtensionTargets')
- tree = ET.ElementTree(root)
- tree.write(ofname, encoding='utf-8', xml_declaration=True)
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
def gen_testproj(self, target_name, ofname):
project_name = target_name
@@ -1189,8 +1182,4 @@ if %%errorlevel%% neq 0 goto :VCEnd'''
ET.SubElement(postbuild, 'Command').text =\
cmd_templ % ('" "'.join(test_command))
ET.SubElement(root, 'Import', Project='$(VCTargetsPath)\Microsoft.Cpp.targets')
- tree = ET.ElementTree(root)
- tree.write(ofname, encoding='utf-8', xml_declaration=True)
- # ElementTree can not do prettyprinting so do it manually
- # doc = xml.dom.minidom.parse(ofname)
- # open(ofname, 'w').write(doc.toprettyxml())
+ self._prettyprint_vcxproj_xml(ET.ElementTree(root), ofname)
diff --git a/mesonbuild/build.py b/mesonbuild/build.py
index 4df7ef5..062b70a 100644
--- a/mesonbuild/build.py
+++ b/mesonbuild/build.py
@@ -1328,12 +1328,16 @@ class CustomTarget(Target):
for c in cmd:
if hasattr(c, 'held_object'):
c = c.held_object
- if isinstance(c, (str, File)):
+ if isinstance(c, str):
+ final_cmd.append(c)
+ elif isinstance(c, File):
+ self.depend_files.append(c)
final_cmd.append(c)
elif isinstance(c, dependencies.ExternalProgram):
if not c.found():
m = 'Tried to use not-found external program {!r} in "command"'
raise InvalidArguments(m.format(c.name))
+ self.depend_files.append(File.from_absolute_file(c.get_path()))
final_cmd += c.get_command()
elif isinstance(c, (BuildTarget, CustomTarget)):
self.dependencies.append(c)
diff --git a/run_unittests.py b/run_unittests.py
index 6ea1d41..7bdd57b 100755
--- a/run_unittests.py
+++ b/run_unittests.py
@@ -346,7 +346,21 @@ class BasePlatformTests(unittest.TestCase):
self.vala_test_dir = os.path.join(src_root, 'test cases/vala')
self.framework_test_dir = os.path.join(src_root, 'test cases/frameworks')
self.unit_test_dir = os.path.join(src_root, 'test cases/unit')
+ # Misc stuff
self.orig_env = os.environ.copy()
+ if self.backend is Backend.ninja:
+ self.no_rebuild_stdout = 'ninja: no work to do.'
+ else:
+ # VS doesn't have a stable output when no changes are done
+ # XCode backend is untested with unit tests, help welcome!
+ self.no_rebuild_stdout = 'UNKNOWN BACKEND {!r}'.format(self.backend.name)
+
+ def ensure_backend_detects_changes(self):
+ # This is needed to increase the difference between build.ninja's
+ # timestamp and the timestamp of whatever you changed due to a Ninja
+ # bug: https://github.com/ninja-build/ninja/issues/371
+ if self.backend is Backend.ninja:
+ time.sleep(1)
def _print_meson_log(self):
log = os.path.join(self.logdir, 'meson-log.txt')
@@ -395,7 +409,7 @@ class BasePlatformTests(unittest.TestCase):
# Add arguments for building the target (if specified),
# and using the build dir (if required, with VS)
args = get_builddir_target_args(self.backend, self.builddir, target)
- self._run(self.build_command + args + extra_args, workdir=self.builddir)
+ return self._run(self.build_command + args + extra_args, workdir=self.builddir)
def clean(self):
dir_args = get_builddir_target_args(self.backend, self.builddir, None)
@@ -421,16 +435,17 @@ class BasePlatformTests(unittest.TestCase):
return self.build(target=target)
def setconf(self, arg, will_build=True):
- # This is needed to increase the difference between build.ninja's
- # timestamp and coredata.dat's timestamp due to a Ninja bug.
- # https://github.com/ninja-build/ninja/issues/371
if will_build:
- time.sleep(1)
+ self.ensure_backend_detects_changes()
self._run(self.mconf_command + [arg, self.builddir])
def wipe(self):
shutil.rmtree(self.builddir)
+ def utime(self, f):
+ self.ensure_backend_detects_changes()
+ os.utime(f)
+
def get_compdb(self):
if self.backend is not Backend.ninja:
raise unittest.SkipTest('Compiler db not available with {} backend'.format(self.backend.name))
@@ -484,6 +499,40 @@ class BasePlatformTests(unittest.TestCase):
path_basename = PurePath(path).parts[-1]
self.assertEqual(PurePath(path_basename), PurePath(basename), msg)
+ def assertBuildIsNoop(self):
+ ret = self.build()
+ if self.backend is Backend.ninja:
+ self.assertEqual(ret.split('\n')[-2], self.no_rebuild_stdout)
+ elif self.backend is Backend.vs:
+ # Ensure that some target said that no rebuild was done
+ 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)
+ 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 assertRebuiltTarget(self, target):
+ ret = self.build()
+ if self.backend is Backend.ninja:
+ self.assertIn('Linking target {}'.format(target), ret)
+ elif self.backend is Backend.vs:
+ # Ensure that this target was rebuilt
+ clre = re.compile('ClCompile:\n [^\n]*cl[^\n]*' + target, flags=re.IGNORECASE)
+ linkre = re.compile('Link:\n [^\n]*link[^\n]*' + target, flags=re.IGNORECASE)
+ self.assertRegex(ret, clre)
+ self.assertRegex(ret, linkre)
+ 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))
+
class AllPlatformTests(BasePlatformTests):
'''
@@ -977,6 +1026,37 @@ 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_source_changes_cause_rebuild(self):
+ '''
+ Test that changes to sources and headers cause rebuilds, but not
+ changes to unused files (as determined by the dependency file) in the
+ input files list.
+ '''
+ testdir = os.path.join(self.common_test_dir, '22 header in file list')
+ self.init(testdir)
+ self.build()
+ # Immediately rebuilding should not do anything
+ self.assertBuildIsNoop()
+ # Changing mtime of header.h should rebuild everything
+ self.utime(os.path.join(testdir, 'header.h'))
+ self.assertRebuiltTarget('prog')
+
+ def test_custom_target_changes_cause_rebuild(self):
+ '''
+ Test that in a custom target, changes to the input files, the
+ ExternalProgram, and any File objects on the command-line cause
+ a rebuild.
+ '''
+ testdir = os.path.join(self.common_test_dir, '64 custom header generator')
+ self.init(testdir)
+ self.build()
+ # Immediately rebuilding should not do anything
+ self.assertBuildIsNoop()
+ # 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')
+
class WindowsTests(BasePlatformTests):
'''
diff --git a/test cases/common/22 header in file list/prog.c b/test cases/common/22 header in file list/prog.c
index 0314ff1..fbedab8 100644
--- a/test cases/common/22 header in file list/prog.c
+++ b/test cases/common/22 header in file list/prog.c
@@ -1 +1,3 @@
+#include "header.h"
+
int main(int argc, char **argv) { return 0; }
diff --git a/test cases/common/64 custom header generator/meson.build b/test cases/common/64 custom header generator/meson.build
index b422401..bcc9a53 100644
--- a/test cases/common/64 custom header generator/meson.build
+++ b/test cases/common/64 custom header generator/meson.build
@@ -5,7 +5,7 @@ gen = find_program('makeheader.py')
generated_h = custom_target('makeheader.py',
output : 'myheader.lh', # Suffix not .h to ensure this works with custom suffixes, too.
input : 'input.def',
-command : [gen, '@INPUT0@', '@OUTPUT0@'])
+command : [gen, '@INPUT0@', '@OUTPUT0@', files('somefile.txt')])
prog = executable('prog', 'prog.c', generated_h)
test('gentest', prog)
diff --git a/test cases/common/64 custom header generator/somefile.txt b/test cases/common/64 custom header generator/somefile.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test cases/common/64 custom header generator/somefile.txt