diff options
author | Jussi Pakkanen <jpakkane@gmail.com> | 2017-04-11 22:58:23 +0300 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-04-11 22:58:23 +0300 |
commit | eaaaee642132d3a568136dc740f74f8aaf4388b0 (patch) | |
tree | a749b2084b5444b34f6ae55fcc13ade3f77777fa | |
parent | 315a52533c077c898033ee1ab0d5f138d7dec905 (diff) | |
parent | 711c0cbd674a84fc2d28d0b92dfb62124180d3ef (diff) | |
download | meson-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.py | 16 | ||||
-rw-r--r-- | mesonbuild/backend/ninjabackend.py | 6 | ||||
-rw-r--r-- | mesonbuild/backend/vs2010backend.py | 39 | ||||
-rw-r--r-- | mesonbuild/build.py | 6 | ||||
-rwxr-xr-x | run_unittests.py | 90 | ||||
-rw-r--r-- | test cases/common/22 header in file list/prog.c | 2 | ||||
-rw-r--r-- | test cases/common/64 custom header generator/meson.build | 2 | ||||
-rw-r--r-- | test cases/common/64 custom header generator/somefile.txt | 0 |
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 " into &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('&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 |