Bug 1454912 - Use a .stub file as the target for all GENERATED_FILES rules; r=nalexander
authorMike Shal <mshal@mozilla.com>
Wed, 09 May 2018 08:24:31 -0400
changeset 418351 1eb04a9bfb7a82eb6fac5e29be7c6b03999d9361
parent 418350 82c74467f638711b16996f2a0dca28a597f32a0d
child 418352 9260cc524bb54d9318075578488de8a6f40677eb
push id33998
push userdluca@mozilla.com
push dateTue, 15 May 2018 21:53:06 +0000
treeherdermozilla-central@9260cc524bb5 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander
bugs1454912
milestone62.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1454912 - Use a .stub file as the target for all GENERATED_FILES rules; r=nalexander The make backend was treating the first output of a GENERATED_FILES rule specially, since it was the target of the rule containing the script invocation. We want the outputs of GENERATED_FILES rules to be FileAvoidWrite so that we avoid triggering downstream rules if the outputs are unchanged, but if the target of the script invocation is FileAvoidWrite, then make may continually re-run the script during a no-op build. The solution here is to use a stub file as the target of the script invocation which will always be touched when the script runs. Since nothing else in the build depends on the stub, we don't need to FileAvoidWrite it. All actual outputs of the script can be FileAvoidWrite, and make can properly avoid work for files that haven't changed. MozReview-Commit-ID: 3GejZw2tpqu
CLOBBER
config/config.mk
python/mozbuild/mozbuild/action/file_generate.py
python/mozbuild/mozbuild/backend/recursivemake.py
python/mozbuild/mozbuild/backend/tup.py
python/mozbuild/mozbuild/test/backend/test_recursivemake.py
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Merge day clobber
\ No newline at end of file
+Bug 1454912 - Changed out GENERATED_FILES are handled in the RecursiveMake backend
--- a/config/config.mk
+++ b/config/config.mk
@@ -24,16 +24,19 @@ topsrcdir	= $(DEPTH)
 endif
 
 ifndef INCLUDED_AUTOCONF_MK
 include $(DEPTH)/config/autoconf.mk
 endif
 
 -include $(DEPTH)/.mozconfig.mk
 
+# MDDEPDIR is the subdirectory where dependency files are stored
+MDDEPDIR := .deps
+
 ifndef EXTERNALLY_MANAGED_MAKE_FILE
 # Import the automatically generated backend file. If this file doesn't exist,
 # the backend hasn't been properly configured. We want this to be a fatal
 # error, hence not using "-include".
 ifndef STANDALONE_MAKEFILE
 GLOBAL_DEPS += backend.mk
 include backend.mk
 endif
@@ -408,19 +411,16 @@ endif # ! WINNT
 
 # Make sure any compiled classes work with at least JVM 1.4
 JAVAC_FLAGS += -source 1.4
 
 ifdef MOZ_DEBUG
 JAVAC_FLAGS += -g
 endif
 
-# MDDEPDIR is the subdirectory where dependency files are stored
-MDDEPDIR := .deps
-
 # $(call CHECK_SYMBOLS,lib,PREFIX,dep_name,test)
 # Checks that the given `lib` doesn't contain dependency on symbols with a
 # version starting with `PREFIX`_ and matching the `test`. `dep_name` is only
 # used for the error message.
 # `test` is an awk expression using the information in the variable `v` which
 # contains a list of version items ([major, minor, ...]).
 define CHECK_SYMBOLS
 @$(TOOLCHAIN_PREFIX)readelf -sW $(1) | \
--- a/python/mozbuild/mozbuild/action/file_generate.py
+++ b/python/mozbuild/mozbuild/action/file_generate.py
@@ -28,16 +28,18 @@ def main(argv):
     parser.add_argument('python_script', metavar='python-script', type=str,
                         help='The Python script to run')
     parser.add_argument('method_name', metavar='method-name', type=str,
                         help='The method of the script to invoke')
     parser.add_argument('output_file', metavar='output-file', type=str,
                         help='The file to generate')
     parser.add_argument('dep_file', metavar='dep-file', type=str,
                         help='File to write any additional make dependencies to')
+    parser.add_argument('dep_target', metavar='dep-target', type=str,
+                        help='Make target to use in the dependencies file')
     parser.add_argument('additional_arguments', metavar='arg',
                         nargs=argparse.REMAINDER,
                         help="Additional arguments to the script's main() method")
 
     args = parser.parse_args(argv)
 
     kwargs = {}
     if args.locale:
@@ -90,17 +92,17 @@ def main(argv):
                 # the script.
                 deps |= set(iter_modules_in_path(buildconfig.topsrcdir,
                                                  buildconfig.topobjdir))
                 # Add dependencies on any buildconfig items that were accessed
                 # by the script.
                 deps |= set(buildconfig.get_dependencies())
 
                 mk = Makefile()
-                mk.create_rule([args.output_file]).add_dependencies(deps)
+                mk.create_rule([args.dep_target]).add_dependencies(deps)
                 with FileAvoidWrite(args.dep_file) as dep_file:
                     mk.dump(dep_file)
         # Even when our file's contents haven't changed, we want to update
         # the file's mtime so make knows this target isn't still older than
         # whatever prerequisite caused it to be built this time around.
         try:
             os.utime(args.output_file, None)
         except:
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -528,16 +528,20 @@ class RecursiveMakeBackend(CommonBackend
                 try:
                     outputs.append(o.format(**substs))
                 except KeyError as e:
                     raise ValueError('%s not in %s is not a valid substitution in %s'
                                      % (e.args[0], ', '.join(sorted(substs.keys())), o))
 
             first_output = outputs[0]
             dep_file = "%s.pp" % first_output
+            # The stub target file needs to go in MDDEPDIR so that it doesn't
+            # get written into generated Android resource directories, breaking
+            # Gradle tooling and/or polluting the Android packages.
+            stub_file = "$(MDDEPDIR)/%s.stub" % first_output
 
             if obj.inputs:
                 if obj.localized:
                     # Localized generated files can have locale-specific inputs, which are
                     # indicated by paths starting with `en-US/` or containing `locales/en-US/`.
                     def srcpath(p):
                         if 'locales/en-US' in p:
                             # We need an "absolute source path" relative to
@@ -572,28 +576,30 @@ class RecursiveMakeBackend(CommonBackend
                 # If we're doing this during export that means we need it during
                 # compile, but if we have an artifact build we don't run compile,
                 # so we can skip it altogether or let the rule run as the result of
                 # something depending on it.
                 if tier != 'export' or not self.environment.is_artifact_build:
                     if not needs_AB_rCD:
                         # Android localized resources have special Makefile
                         # handling.
-                        backend_file.write('%s:: %s\n' % (tier, first_output))
+                        backend_file.write('%s:: %s\n' % (tier, stub_file))
                 for output in outputs:
-                    if output != first_output:
-                        backend_file.write('%s: %s ;\n' % (output, first_output))
+                    backend_file.write('%s: %s ;\n' % (output, stub_file))
                     backend_file.write('GARBAGE += %s\n' % output)
+                backend_file.write('GARBAGE += %s\n' % stub_file)
                 backend_file.write('EXTRA_MDDEPEND_FILES += %s\n' % dep_file)
 
-                backend_file.write("""{output}: {script}{inputs}{backend}{force}
+                backend_file.write("""{stub}: {script}{inputs}{backend}{force}
 \t$(REPORT_BUILD)
-\t$(call py_action,file_generate,{locale}{script} {method} {output} $(MDDEPDIR)/{dep_file}{inputs}{flags})
+\t$(call py_action,file_generate,{locale}{script} {method} {output} $(MDDEPDIR)/{dep_file} {stub}{inputs}{flags})
+\t@$(TOUCH) $@
 
-""".format(output=first_output,
+""".format(stub=stub_file,
+           output=first_output,
            dep_file=dep_file,
            inputs=' ' + ' '.join(inputs) if inputs else '',
            flags=' ' + ' '.join(shell_quote(f) for f in obj.flags) if obj.flags else '',
            backend=' backend.mk' if obj.flags else '',
            # Locale repacks repack multiple locales from a single configured objdir,
            # so standard mtime dependencies won't work properly when the build is re-run
            # with a different locale as input. IS_LANGUAGE_REPACK will reliably be set
            # in this situation, so simply force the generation to run in that case.
--- a/python/mozbuild/mozbuild/backend/tup.py
+++ b/python/mozbuild/mozbuild/backend/tup.py
@@ -624,16 +624,17 @@ class TupBackend(CommonBackend):
             cmd = self._py_action('file_generate')
             if obj.localized:
                 cmd.append('--locale=en-US')
             cmd.extend([
                 obj.script,
                 obj.method,
                 obj.outputs[0],
                 '%s.pp' % obj.outputs[0], # deps file required
+                'unused', # deps target is required
             ])
             full_inputs = [f.full_path for f in obj.inputs]
             cmd.extend(full_inputs)
             cmd.extend(shell_quote(f) for f in obj.flags)
 
             outputs = []
             outputs.extend(obj.outputs)
             outputs.append('%s.pp' % obj.outputs[0])
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -391,76 +391,91 @@ class TestRecursiveMakeBackend(BackendTe
     def test_generated_files(self):
         """Ensure GENERATED_FILES is handled properly."""
         env = self._consume('generated-files', RecursiveMakeBackend)
 
         backend_path = mozpath.join(env.topobjdir, 'backend.mk')
         lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
 
         expected = [
-            'export:: bar.c',
+            'export:: $(MDDEPDIR)/bar.c.stub',
+            'bar.c: $(MDDEPDIR)/bar.c.stub ;',
             'GARBAGE += bar.c',
+            'GARBAGE += $(MDDEPDIR)/bar.c.stub',
             'EXTRA_MDDEPEND_FILES += bar.c.pp',
-            'bar.c: %s/generate-bar.py' % env.topsrcdir,
+            '$(MDDEPDIR)/bar.c.stub: %s/generate-bar.py' % env.topsrcdir,
             '$(REPORT_BUILD)',
-            '$(call py_action,file_generate,%s/generate-bar.py baz bar.c $(MDDEPDIR)/bar.c.pp)' % env.topsrcdir,
+            '$(call py_action,file_generate,%s/generate-bar.py baz bar.c $(MDDEPDIR)/bar.c.pp $(MDDEPDIR)/bar.c.stub)' % env.topsrcdir,
+            '@$(TOUCH) $@',
             '',
-            'export:: foo.c',
+            'export:: $(MDDEPDIR)/foo.c.stub',
+            'foo.c: $(MDDEPDIR)/foo.c.stub ;',
             'GARBAGE += foo.c',
+            'GARBAGE += $(MDDEPDIR)/foo.c.stub',
             'EXTRA_MDDEPEND_FILES += foo.c.pp',
-            'foo.c: %s/generate-foo.py $(srcdir)/foo-data' % (env.topsrcdir),
+            '$(MDDEPDIR)/foo.c.stub: %s/generate-foo.py $(srcdir)/foo-data' % (env.topsrcdir),
             '$(REPORT_BUILD)',
-            '$(call py_action,file_generate,%s/generate-foo.py main foo.c $(MDDEPDIR)/foo.c.pp $(srcdir)/foo-data)' % (env.topsrcdir),
+            '$(call py_action,file_generate,%s/generate-foo.py main foo.c $(MDDEPDIR)/foo.c.pp $(MDDEPDIR)/foo.c.stub $(srcdir)/foo-data)' % (env.topsrcdir),
+            '@$(TOUCH) $@',
             '',
         ]
 
         self.maxDiff = None
         self.assertEqual(lines, expected)
 
     def test_generated_files_force(self):
         """Ensure GENERATED_FILES with .force is handled properly."""
         env = self._consume('generated-files-force', RecursiveMakeBackend)
 
         backend_path = mozpath.join(env.topobjdir, 'backend.mk')
         lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
 
         expected = [
-            'export:: bar.c',
+            'export:: $(MDDEPDIR)/bar.c.stub',
+            'bar.c: $(MDDEPDIR)/bar.c.stub ;',
             'GARBAGE += bar.c',
+            'GARBAGE += $(MDDEPDIR)/bar.c.stub',
             'EXTRA_MDDEPEND_FILES += bar.c.pp',
-            'bar.c: %s/generate-bar.py FORCE' % env.topsrcdir,
+            '$(MDDEPDIR)/bar.c.stub: %s/generate-bar.py FORCE' % env.topsrcdir,
             '$(REPORT_BUILD)',
-            '$(call py_action,file_generate,%s/generate-bar.py baz bar.c $(MDDEPDIR)/bar.c.pp)' % env.topsrcdir,
+            '$(call py_action,file_generate,%s/generate-bar.py baz bar.c $(MDDEPDIR)/bar.c.pp $(MDDEPDIR)/bar.c.stub)' % env.topsrcdir,
+            '@$(TOUCH) $@',
             '',
-            'export:: foo.c',
+            'export:: $(MDDEPDIR)/foo.c.stub',
+            'foo.c: $(MDDEPDIR)/foo.c.stub ;',
             'GARBAGE += foo.c',
+            'GARBAGE += $(MDDEPDIR)/foo.c.stub',
             'EXTRA_MDDEPEND_FILES += foo.c.pp',
-            'foo.c: %s/generate-foo.py $(srcdir)/foo-data' % (env.topsrcdir),
+            '$(MDDEPDIR)/foo.c.stub: %s/generate-foo.py $(srcdir)/foo-data' % (env.topsrcdir),
             '$(REPORT_BUILD)',
-            '$(call py_action,file_generate,%s/generate-foo.py main foo.c $(MDDEPDIR)/foo.c.pp $(srcdir)/foo-data)' % (env.topsrcdir),
+            '$(call py_action,file_generate,%s/generate-foo.py main foo.c $(MDDEPDIR)/foo.c.pp $(MDDEPDIR)/foo.c.stub $(srcdir)/foo-data)' % (env.topsrcdir),
+            '@$(TOUCH) $@',
             '',
         ]
 
         self.maxDiff = None
         self.assertEqual(lines, expected)
 
     def test_localized_generated_files(self):
         """Ensure LOCALIZED_GENERATED_FILES is handled properly."""
         env = self._consume('localized-generated-files', RecursiveMakeBackend)
 
         backend_path = mozpath.join(env.topobjdir, 'backend.mk')
         lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
 
         expected = [
-            'libs:: foo.xyz',
+            'libs:: $(MDDEPDIR)/foo.xyz.stub',
+            'foo.xyz: $(MDDEPDIR)/foo.xyz.stub ;',
             'GARBAGE += foo.xyz',
+            'GARBAGE += $(MDDEPDIR)/foo.xyz.stub',
             'EXTRA_MDDEPEND_FILES += foo.xyz.pp',
-            'foo.xyz: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)' % env.topsrcdir,
+            '$(MDDEPDIR)/foo.xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)' % env.topsrcdir,
             '$(REPORT_BUILD)',
-            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo.xyz $(MDDEPDIR)/foo.xyz.pp $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo.xyz $(MDDEPDIR)/foo.xyz.pp $(MDDEPDIR)/foo.xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '@$(TOUCH) $@',
             '',
             'LOCALIZED_FILES_0_FILES += foo.xyz',
             'LOCALIZED_FILES_0_DEST = $(FINAL_TARGET)/',
             'LOCALIZED_FILES_0_TARGET := libs',
             'INSTALL_TARGETS += LOCALIZED_FILES_0',
         ]
 
         self.maxDiff = None
@@ -469,63 +484,78 @@ class TestRecursiveMakeBackend(BackendTe
     def test_localized_generated_files_force(self):
         """Ensure LOCALIZED_GENERATED_FILES with .force is handled properly."""
         env = self._consume('localized-generated-files-force', RecursiveMakeBackend)
 
         backend_path = mozpath.join(env.topobjdir, 'backend.mk')
         lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
 
         expected = [
-            'libs:: foo.xyz',
+            'libs:: $(MDDEPDIR)/foo.xyz.stub',
+            'foo.xyz: $(MDDEPDIR)/foo.xyz.stub ;',
             'GARBAGE += foo.xyz',
+            'GARBAGE += $(MDDEPDIR)/foo.xyz.stub',
             'EXTRA_MDDEPEND_FILES += foo.xyz.pp',
-            'foo.xyz: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)' % env.topsrcdir,
+            '$(MDDEPDIR)/foo.xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)' % env.topsrcdir,
             '$(REPORT_BUILD)',
-            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo.xyz $(MDDEPDIR)/foo.xyz.pp $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo.xyz $(MDDEPDIR)/foo.xyz.pp $(MDDEPDIR)/foo.xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '@$(TOUCH) $@',
             '',
-            'libs:: abc.xyz',
+            'libs:: $(MDDEPDIR)/abc.xyz.stub',
+            'abc.xyz: $(MDDEPDIR)/abc.xyz.stub ;',
             'GARBAGE += abc.xyz',
+            'GARBAGE += $(MDDEPDIR)/abc.xyz.stub',
             'EXTRA_MDDEPEND_FILES += abc.xyz.pp',
-            'abc.xyz: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input FORCE' % env.topsrcdir,
+            '$(MDDEPDIR)/abc.xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input FORCE' % env.topsrcdir,
             '$(REPORT_BUILD)',
-            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main abc.xyz $(MDDEPDIR)/abc.xyz.pp $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main abc.xyz $(MDDEPDIR)/abc.xyz.pp $(MDDEPDIR)/abc.xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '@$(TOUCH) $@',
             '',
         ]
 
         self.maxDiff = None
         self.assertEqual(lines, expected)
 
     def test_localized_generated_files_AB_CD(self):
         """Ensure LOCALIZED_GENERATED_FILES is handled properly
         when {AB_CD} and {AB_rCD} are used."""
         env = self._consume('localized-generated-files-AB_CD', RecursiveMakeBackend)
 
         backend_path = mozpath.join(env.topobjdir, 'backend.mk')
         lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
 
         expected = [
-            'libs:: foo$(AB_CD).xyz',
+            'libs:: $(MDDEPDIR)/foo$(AB_CD).xyz.stub',
+            'foo$(AB_CD).xyz: $(MDDEPDIR)/foo$(AB_CD).xyz.stub ;',
             'GARBAGE += foo$(AB_CD).xyz',
+            'GARBAGE += $(MDDEPDIR)/foo$(AB_CD).xyz.stub',
             'EXTRA_MDDEPEND_FILES += foo$(AB_CD).xyz.pp',
-            'foo$(AB_CD).xyz: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)' % env.topsrcdir,
+            '$(MDDEPDIR)/foo$(AB_CD).xyz.stub: %s/generate-foo.py $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)' % env.topsrcdir,
             '$(REPORT_BUILD)',
-            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo$(AB_CD).xyz $(MDDEPDIR)/foo$(AB_CD).xyz.pp $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main foo$(AB_CD).xyz $(MDDEPDIR)/foo$(AB_CD).xyz.pp $(MDDEPDIR)/foo$(AB_CD).xyz.stub $(call MERGE_FILE,localized-input) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '@$(TOUCH) $@',
             '',
             'include $(topsrcdir)/config/AB_rCD.mk',
+            'bar$(AB_rCD).xyz: $(MDDEPDIR)/bar$(AB_rCD).xyz.stub ;',
             'GARBAGE += bar$(AB_rCD).xyz',
+            'GARBAGE += $(MDDEPDIR)/bar$(AB_rCD).xyz.stub',
             'EXTRA_MDDEPEND_FILES += bar$(AB_rCD).xyz.pp',
-            'bar$(AB_rCD).xyz: %s/generate-foo.py $(call MERGE_RELATIVE_FILE,localized-input,inner/locales) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)' % env.topsrcdir,
+            '$(MDDEPDIR)/bar$(AB_rCD).xyz.stub: %s/generate-foo.py $(call MERGE_RELATIVE_FILE,localized-input,inner/locales) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)' % env.topsrcdir,
             '$(REPORT_BUILD)',
-            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main bar$(AB_rCD).xyz $(MDDEPDIR)/bar$(AB_rCD).xyz.pp $(call MERGE_RELATIVE_FILE,localized-input,inner/locales) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main bar$(AB_rCD).xyz $(MDDEPDIR)/bar$(AB_rCD).xyz.pp $(MDDEPDIR)/bar$(AB_rCD).xyz.stub $(call MERGE_RELATIVE_FILE,localized-input,inner/locales) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '@$(TOUCH) $@',
             '',
+            'zot$(AB_rCD).xyz: $(MDDEPDIR)/zot$(AB_rCD).xyz.stub ;',
             'GARBAGE += zot$(AB_rCD).xyz',
+            'GARBAGE += $(MDDEPDIR)/zot$(AB_rCD).xyz.stub',
             'EXTRA_MDDEPEND_FILES += zot$(AB_rCD).xyz.pp',
-            'zot$(AB_rCD).xyz: %s/generate-foo.py $(call MERGE_RELATIVE_FILE,localized-input,locales) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)' % env.topsrcdir,
+            '$(MDDEPDIR)/zot$(AB_rCD).xyz.stub: %s/generate-foo.py $(call MERGE_RELATIVE_FILE,localized-input,locales) $(srcdir)/non-localized-input $(if $(IS_LANGUAGE_REPACK),FORCE)' % env.topsrcdir,
             '$(REPORT_BUILD)',
-            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main zot$(AB_rCD).xyz $(MDDEPDIR)/zot$(AB_rCD).xyz.pp $(call MERGE_RELATIVE_FILE,localized-input,locales) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '$(call py_action,file_generate,--locale=$(AB_CD) %s/generate-foo.py main zot$(AB_rCD).xyz $(MDDEPDIR)/zot$(AB_rCD).xyz.pp $(MDDEPDIR)/zot$(AB_rCD).xyz.stub $(call MERGE_RELATIVE_FILE,localized-input,locales) $(srcdir)/non-localized-input)' % env.topsrcdir,
+            '@$(TOUCH) $@',
             '',
         ]
 
         self.maxDiff = None
         self.assertEqual(lines, expected)
 
     def test_exports_generated(self):
         """Ensure EXPORTS that are listed in GENERATED_FILES