Bug 848530 - Check for moz.build traversal at top of build; r=glandium
authorGregory Szorc <gps@mozilla.com>
Fri, 17 May 2013 10:54:56 -0700
changeset 132260 d14e9efe0b005c4b47234c12cd82e762eb17cb2f
parent 132259 4b3f1fe5c0971bfc6c13a5bd234b2a37896b0a0b
child 132261 5c4392f60d581c4294d42aaff80887cb2b00828e
push id28185
push usergszorc@mozilla.com
push dateFri, 17 May 2013 17:55:33 +0000
treeherdermozilla-inbound@d14e9efe0b00 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersglandium
bugs848530
milestone24.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 848530 - Check for moz.build traversal at top of build; r=glandium One of the first actions an invoked Makefile now does is check to see if *any* moz.build file or Makefile.in is out of date. If so, config.status is executed to rebuild the build backend. Since we always perform this check as part of a build, we no longer need special handling for out of date moz.build files during traversals. This results in the removal of a significant amount of code! Another upside of the change is that if a moz.build file is modified during building, we don't (potentially) modify the build backend from under the in-progress build. Thus the only race condition that remains is if a moz.build is mutated during moz.build reading. This window (a few seconds) is significantly shorter than the time of a full build (minutes). This patch should also enable us to remove empty Makefile.in files without requiring a clobber.
CLOBBER
Makefile.in
config/rules.mk
js/src/config/rules.mk
python/mozbuild/mozbuild/backend/base.py
python/mozbuild/mozbuild/backend/recursivemake.py
python/mozbuild/mozbuild/test/backend/test_recursivemake.py
--- a/CLOBBER
+++ b/CLOBBER
@@ -12,9 +12,12 @@
 #          O               O
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
-Bug 852687 - changing an idl without clobbering resulted test failures
+Bug 848530 - Added a dependency file for moz.build traversal.
+
+Alternative to clobber is to run ./config.status from the objdir and to
+touch the CLOBBER file in the objdir.
--- a/Makefile.in
+++ b/Makefile.in
@@ -33,17 +33,17 @@ include $(topsrcdir)/config/config.mk
 GARBAGE_DIRS += dist _javagen _profile _tests staticlib
 DIST_GARBAGE = config.cache config.log config.status* config-defs.h \
    config/autoconf.mk \
    unallmakefiles mozilla-config.h \
    netwerk/necko-config.h xpcom/xpcom-config.h xpcom/xpcom-private.h \
    $(topsrcdir)/.mozconfig.mk $(topsrcdir)/.mozconfig.out
 
 ifndef MOZ_PROFILE_USE
-default alldep all:: CLOBBER $(topsrcdir)/configure config.status
+default alldep all:: CLOBBER $(topsrcdir)/configure config.status backend.RecursiveMakeBackend.built
 	$(RM) -r $(DIST)/sdk
 	$(RM) -r $(DIST)/include
 	$(RM) -r $(DIST)/private
 	$(RM) -r $(DIST)/public
 	$(RM) -r $(DIST)/bin
 	$(RM) -r _tests
 endif
 
@@ -68,16 +68,28 @@ config.status: $(topsrcdir)/configure
 	@exit 1
 
 # Build pseudo-external modules first when export is explicitly called
 export::
 	$(RM) -r $(DIST)/sdk
 	$(MAKE) -C config export
 	$(MAKE) tier_nspr
 
+backend.RecursiveMakeBackend.built:
+	@echo "Updating build backend because of moz.build changes."
+	@$(PYTHON) ./config.status
+
+ifdef .PYMAKE
+includedeps backend.RecursiveMakeBackend.built.pp
+else
+include backend.RecursiveMakeBackend.built.pp
+endif
+
+export MOZBUILD_BACKEND_CHECKED=1
+
 ifdef ENABLE_TESTS
 # Additional makefile targets to call automated test suites
 include $(topsrcdir)/testing/testsuite-targets.mk
 endif
 
 include $(topsrcdir)/config/rules.mk
 
 distclean::
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -606,16 +606,27 @@ endif
 
 ifeq (,$(CROSS_COMPILE))
 HOST_OUTOPTION = $(OUTOPTION)
 else
 HOST_OUTOPTION = -o # eol
 endif
 ################################################################################
 
+# Regenerate the build backend if it is out of date. We only check this once
+# per traversal, hence the ifdef and the export. This rule needs to come before
+# other rules for the default target or else it may not run in time.
+ifndef MOZBUILD_BACKEND_CHECKED
+default::
+	$(MAKE) -C $(DEPTH) backend.RecursiveMakeBackend.built
+
+export MOZBUILD_BACKEND_CHECKED=1
+endif
+
+
 # SUBMAKEFILES: List of Makefiles for next level down.
 #   This is used to update or create the Makefiles before invoking them.
 SUBMAKEFILES += $(addsuffix /Makefile, $(DIRS) $(TOOL_DIRS) $(PARALLEL_DIRS))
 
 # The root makefile doesn't want to do a plain export/libs, because
 # of the tiers and because of libxul. Suppress the default rules in favor
 # of something else. Makefiles which use this var *must* provide a sensible
 # default rule before including rules.mk
@@ -1185,36 +1196,16 @@ endif
 	$(JAR) cf $@ -C $(_JAVA_DIR) .
 
 GARBAGE_DIRS += $(_JAVA_DIR)
 
 ###############################################################################
 # Update Files Managed by Build Backend
 ###############################################################################
 
-ifdef MOZBUILD_DERIVED
-
-# If this Makefile is derived from moz.build files, substitution for all .in
-# files is handled by SUBSTITUTE_FILES. This includes Makefile.in.
-ifneq ($(SUBSTITUTE_FILES),,)
-$(SUBSTITUTE_FILES): % : $(srcdir)/%.in $(DEPTH)/config/autoconf.mk
-	@$(PYTHON) $(DEPTH)/config.status -n --file=$@
-	@$(TOUCH) $@
-endif
-
-# Detect when the backend.mk needs rebuilt. This will cause a full scan and
-# rebuild. While relatively expensive, it should only occur once per recursion.
-ifneq ($(BACKEND_INPUT_FILES),,)
-backend.mk: $(BACKEND_INPUT_FILES)
-	@$(PYTHON) $(DEPTH)/config.status -n
-	@$(TOUCH) $@
-endif
-
-endif # MOZBUILD_DERIVED
-
 ifndef NO_MAKEFILE_RULE
 Makefile: Makefile.in
 	@$(PYTHON) $(DEPTH)/config.status -n --file=Makefile
 	@$(TOUCH) $@
 endif
 
 ifndef NO_SUBMAKEFILES_RULE
 ifdef SUBMAKEFILES
--- a/js/src/config/rules.mk
+++ b/js/src/config/rules.mk
@@ -606,16 +606,27 @@ endif
 
 ifeq (,$(CROSS_COMPILE))
 HOST_OUTOPTION = $(OUTOPTION)
 else
 HOST_OUTOPTION = -o # eol
 endif
 ################################################################################
 
+# Regenerate the build backend if it is out of date. We only check this once
+# per traversal, hence the ifdef and the export. This rule needs to come before
+# other rules for the default target or else it may not run in time.
+ifndef MOZBUILD_BACKEND_CHECKED
+default::
+	$(MAKE) -C $(DEPTH) backend.RecursiveMakeBackend.built
+
+export MOZBUILD_BACKEND_CHECKED=1
+endif
+
+
 # SUBMAKEFILES: List of Makefiles for next level down.
 #   This is used to update or create the Makefiles before invoking them.
 SUBMAKEFILES += $(addsuffix /Makefile, $(DIRS) $(TOOL_DIRS) $(PARALLEL_DIRS))
 
 # The root makefile doesn't want to do a plain export/libs, because
 # of the tiers and because of libxul. Suppress the default rules in favor
 # of something else. Makefiles which use this var *must* provide a sensible
 # default rule before including rules.mk
@@ -1185,36 +1196,16 @@ endif
 	$(JAR) cf $@ -C $(_JAVA_DIR) .
 
 GARBAGE_DIRS += $(_JAVA_DIR)
 
 ###############################################################################
 # Update Files Managed by Build Backend
 ###############################################################################
 
-ifdef MOZBUILD_DERIVED
-
-# If this Makefile is derived from moz.build files, substitution for all .in
-# files is handled by SUBSTITUTE_FILES. This includes Makefile.in.
-ifneq ($(SUBSTITUTE_FILES),,)
-$(SUBSTITUTE_FILES): % : $(srcdir)/%.in $(DEPTH)/config/autoconf.mk
-	@$(PYTHON) $(DEPTH)/config.status -n --file=$@
-	@$(TOUCH) $@
-endif
-
-# Detect when the backend.mk needs rebuilt. This will cause a full scan and
-# rebuild. While relatively expensive, it should only occur once per recursion.
-ifneq ($(BACKEND_INPUT_FILES),,)
-backend.mk: $(BACKEND_INPUT_FILES)
-	@$(PYTHON) $(DEPTH)/config.status -n
-	@$(TOUCH) $@
-endif
-
-endif # MOZBUILD_DERIVED
-
 ifndef NO_MAKEFILE_RULE
 Makefile: Makefile.in
 	@$(PYTHON) $(DEPTH)/config.status -n --file=Makefile
 	@$(TOUCH) $@
 endif
 
 ifndef NO_SUBMAKEFILES_RULE
 ifdef SUBMAKEFILES
--- a/python/mozbuild/mozbuild/backend/base.py
+++ b/python/mozbuild/mozbuild/backend/base.py
@@ -99,16 +99,20 @@ class BuildBackend(LoggingMixin):
     def __init__(self, environment):
         assert isinstance(environment, ConfigEnvironment)
 
         self.populate_logger()
 
         self.environment = environment
         self.summary = BackendConsumeSummary()
 
+        # Files whose modification should cause a new read and backend
+        # generation.
+        self.backend_input_files = set()
+
         self._environments = {}
         self._environments[environment.topobjdir] = environment
 
         self._init()
 
     def _init():
         """Hook point for child classes to perform actions during __init__.
 
@@ -148,16 +152,19 @@ class BuildBackend(LoggingMixin):
         backend_time = 0.0
 
         for obj in objs:
             self.summary.object_count += 1
             obj_start = time.time()
             self.consume_object(obj)
             backend_time += time.time() - obj_start
 
+            if isinstance(obj, SandboxDerived):
+                self.backend_input_files |= obj.sandbox_all_paths
+
             if isinstance(obj, ReaderSummary):
                 self.summary.mozbuild_count = obj.total_file_count
                 self.summary.mozbuild_execution_time = obj.total_execution_time
 
         # Write out a file indicating when this backend was last generated.
         age_file = os.path.join(self.environment.topobjdir,
             'backend.%s.built' % self.__class__.__name__)
         with open(age_file, 'a'):
--- a/python/mozbuild/mozbuild/backend/recursivemake.py
+++ b/python/mozbuild/mozbuild/backend/recursivemake.py
@@ -53,94 +53,46 @@ class BackendMakeFile(object):
     tree is scanned as opposed to individual files), if we were to blindly
     write backend.mk files, the net effect of updating a single mozbuild file
     in the tree is all backend.mk files have new mtimes. This would in turn
     invalidate all make targets across the whole tree! This would effectively
     undermine incremental builds as any mozbuild change would cause the entire
     tree to rebuild!
 
     The solution is to not update the mtimes of backend.mk files unless they
-    actually change. We use FileAvoidWrite to accomplish this. However, this
-    puts us in a somewhat complicated position when it comes to tree recursion.
-    As you are recursing the tree, the first time you come across a backend.mk
-    that is out of date, a full tree build will be incurred. In typical make
-    build systems, we would touch the out-of-date target (backend.mk) to ensure
-    its mtime is newer than all its dependencies - even if the contents did
-    not change. However, we can't rely on just this approach. During recursion,
-    the first trigger of backend generation will cause only that backend.mk to
-    update. If there is another backend.mk that is also out of date according
-    to mtime but whose contents were not changed, when we recurse to that
-    directory, make will trigger another full backend generation! This would
-    be completely redundant and would slow down builds! This is not acceptable.
-
-    We work around this problem by having backend generation update the mtime
-    of backend.mk if they are older than their inputs - even if the file
-    contents did not change. This is essentially a middle ground between
-    always updating backend.mk and only updating the backend.mk that was out
-    of date during recursion.
+    actually change. We use FileAvoidWrite to accomplish this.
     """
 
     def __init__(self, srcdir, objdir, environment):
         self.srcdir = srcdir
         self.objdir = objdir
         self.environment = environment
         self.path = os.path.join(objdir, 'backend.mk')
 
-        # Filenames that influenced the content of this file.
-        self.inputs = set()
-
         self.fh = FileAvoidWrite(self.path)
         self.fh.write('# THIS FILE WAS AUTOMATICALLY GENERATED. DO NOT EDIT.\n')
         self.fh.write('\n')
         self.fh.write('MOZBUILD_DERIVED := 1\n')
 
-        # SUBSTITUTE_FILES handles Makefile.in -> Makefile conversion. This
-        # also doubles to handle the case where there is no Makefile.in.
+        # The global rule to incur backend generation generates Makefiles.
         self.fh.write('NO_MAKEFILE_RULE := 1\n')
 
         # We can't blindly have a SUBMAKEFILES rule because some of the
         # Makefile may not have a corresponding Makefile.in. For the case
         # where a new directory is added, the mozbuild file referencing that
         # new directory will need updated. This will cause a full backend
         # scan and build, installing the new Makefile.
         self.fh.write('NO_SUBMAKEFILES_RULE := 1\n')
 
 
     def write(self, buf):
         self.fh.write(buf)
 
     def close(self):
-        if self.inputs:
-            l = ' '.join(sorted(self.inputs))
-            self.fh.write('BACKEND_INPUT_FILES += %s\n' % l)
-
-        result = self.fh.close()
-
-        if not self.inputs:
-            return result
-
-        # Update mtime iff any of its input files are newer. See class notes
-        # for why we do this.
-        existing_mtime = os.path.getmtime(self.path)
-
-        def mtime(path):
-            try:
-                return os.path.getmtime(path)
-            except OSError as e:
-                if e.errno == errno.ENOENT:
-                    return 0
-
-                raise
-
-        input_mtime = max(mtime(path) for path in self.inputs)
-
-        if input_mtime > existing_mtime:
-            os.utime(self.path, None)
-
-        return result
+        return self.fh.close()
 
 
 class RecursiveMakeBackend(BuildBackend):
     """Backend that integrates with the existing recursive make build system.
 
     This backend facilitates the transition from Makefile.in to moz.build
     files.
 
@@ -165,16 +117,19 @@ class RecursiveMakeBackend(BuildBackend)
             return '{:d} total backend files. {:d} created; {:d} updated; {:d} unchanged'.format(
                 summary.managed_count, summary.created_count,
                 summary.updated_count, summary.unchanged_count)
 
         # This is a little kludgy and could be improved with a better API.
         self.summary.backend_detailed_summary = types.MethodType(detailed,
             self.summary)
 
+        self.backend_input_files.add(os.path.join(self.environment.topobjdir,
+            'config', 'autoconf.mk'))
+
     def _update_from_avoid_write(self, result):
         existed, updated = result
 
         if not existed:
             self.summary.created_count += 1
         elif updated:
             self.summary.updated_count += 1
         else:
@@ -184,29 +139,22 @@ class RecursiveMakeBackend(BuildBackend)
         """Write out build files necessary to build with recursive make."""
 
         if not isinstance(obj, SandboxDerived):
             return
 
         backend_file = self._backend_files.get(obj.srcdir,
             BackendMakeFile(obj.srcdir, obj.objdir, self.get_environment(obj)))
 
-        # Define the paths that will trigger a backend rebuild. We always
-        # add autoconf.mk because that is proxy for CONFIG. We can't use
-        # config.status because there is no make target for that!
-        autoconf_path = os.path.join(obj.topobjdir, 'config', 'autoconf.mk')
-        backend_file.inputs.add(autoconf_path)
-        backend_file.inputs |= obj.sandbox_all_paths
-
         if isinstance(obj, DirectoryTraversal):
             self._process_directory_traversal(obj, backend_file)
         elif isinstance(obj, ConfigFileSubstitution):
-            backend_file.write('SUBSTITUTE_FILES += %s\n' % obj.relpath)
             self._update_from_avoid_write(
                 backend_file.environment.create_config_file(obj.output_path))
+            self.backend_input_files.add(obj.input_path)
             self.summary.managed_count += 1
         elif isinstance(obj, VariablePassthru):
             # Sorted so output is consistent and we don't bump mtimes.
             for k, v in sorted(obj.variables.items()):
                 if isinstance(v, list):
                     for item in v:
                         backend_file.write('%s += %s\n' % (k, item))
 
@@ -242,17 +190,22 @@ class RecursiveMakeBackend(BuildBackend)
             if os.path.exists(makefile_in):
                 self.log(logging.DEBUG, 'substitute_makefile',
                     {'path': makefile}, 'Substituting makefile: {path}')
 
                 self._update_from_avoid_write(
                     bf.environment.create_config_file(makefile))
                 self.summary.managed_count += 1
 
-                bf.write('SUBSTITUTE_FILES += Makefile\n')
+                # Adding the Makefile.in here has the desired side-effect that
+                # if the Makefile.in disappears, this will force moz.build
+                # traversal. This means that when we remove empty Makefile.in
+                # files, the old file will get replaced with the autogenerated
+                # one automatically.
+                self.backend_input_files.add(makefile_in)
             else:
                 self.log(logging.DEBUG, 'stub_makefile',
                     {'path': makefile}, 'Creating stub Makefile: {path}')
 
                 params = {
                     'topsrc': bf.environment.get_top_srcdir(makefile),
                     'src': bf.environment.get_file_srcdir(makefile),
                     'depth': bf.environment.get_depth(makefile),
@@ -262,16 +215,29 @@ class RecursiveMakeBackend(BuildBackend)
                 aw = FileAvoidWrite(makefile)
                 aw.write(STUB_MAKEFILE.format(**params))
                 self._update_from_avoid_write(aw.close())
                 self.summary.managed_count += 1
 
             self._update_from_avoid_write(bf.close())
             self.summary.managed_count += 1
 
+        # Write out a dependency file used to determine whether a config.status
+        # re-run is needed.
+        backend_built_path = os.path.join(self.environment.topobjdir,
+            'backend.%s.built' % self.__class__.__name__)
+        backend_deps = FileAvoidWrite('%s.pp' % backend_built_path)
+        inputs = sorted(self.backend_input_files)
+        backend_deps.write('%s: %s\n' % (backend_built_path, ' '.join(inputs)))
+        for path in inputs:
+            backend_deps.write('%s:\n' % path)
+
+        self._update_from_avoid_write(backend_deps.close())
+        self.summary.managed_count += 1
+
     def _process_directory_traversal(self, obj, backend_file):
         """Process a data.DirectoryTraversal instance."""
         fh = backend_file.fh
 
         for tier, dirs in obj.tier_dirs.iteritems():
             fh.write('TIERS += %s\n' % tier)
 
             if dirs:
--- a/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
+++ b/python/mozbuild/mozbuild/test/backend/test_recursivemake.py
@@ -18,16 +18,18 @@ from mozbuild.test.backend.common import
 
 
 class TestRecursiveMakeBackend(BackendTester):
     def test_basic(self):
         """Ensure the RecursiveMakeBackend works without error."""
         env = self._consume('stub0', RecursiveMakeBackend)
         self.assertTrue(os.path.exists(os.path.join(env.topobjdir,
             'backend.RecursiveMakeBackend.built')))
+        self.assertTrue(os.path.exists(os.path.join(env.topobjdir,
+            'backend.RecursiveMakeBackend.built.pp')))
 
     def test_output_files(self):
         """Ensure proper files are generated."""
         env = self._consume('stub0', RecursiveMakeBackend)
 
         expected = ['', 'dir1', 'dir2']
 
         for d in expected:
@@ -68,25 +70,24 @@ class TestRecursiveMakeBackend(BackendTe
         self.assertTrue(lines[0].startswith('# THIS FILE WAS AUTOMATICALLY'))
 
     def test_backend_mk(self):
         """Ensure backend.mk file is written out properly."""
         env = self._consume('stub0', RecursiveMakeBackend)
 
         p = os.path.join(env.topobjdir, 'backend.mk')
 
-        lines = [l.strip() for l in open(p, 'rt').readlines()[2:-1]]
+        lines = [l.strip() for l in open(p, 'rt').readlines()[2:]]
         self.assertEqual(lines, [
             'MOZBUILD_DERIVED := 1',
             'NO_MAKEFILE_RULE := 1',
             'NO_SUBMAKEFILES_RULE := 1',
             'DIRS := dir1',
             'PARALLEL_DIRS := dir2',
             'TEST_DIRS := dir3',
-            'SUBSTITUTE_FILES += Makefile',
         ])
 
     def test_mtime_no_change(self):
         """Ensure mtime is not updated if file content does not change."""
 
         env = self._consume('stub0', RecursiveMakeBackend)
 
         makefile_path = os.path.join(env.topobjdir, 'Makefile')
@@ -102,26 +103,25 @@ class TestRecursiveMakeBackend(BackendTe
         self.assertEqual(os.path.getmtime(makefile_path), makefile_mtime)
         self.assertEqual(os.path.getmtime(backend_path), backend_mtime)
 
     def test_external_make_dirs(self):
         """Ensure we have make recursion into external make directories."""
         env = self._consume('external_make_dirs', RecursiveMakeBackend)
 
         backend_path = os.path.join(env.topobjdir, 'backend.mk')
-        lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:-1]]
+        lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
         self.assertEqual(lines, [
             'MOZBUILD_DERIVED := 1',
             'NO_MAKEFILE_RULE := 1',
             'NO_SUBMAKEFILES_RULE := 1',
             'DIRS := dir',
             'PARALLEL_DIRS := p_dir',
             'DIRS += external',
             'PARALLEL_DIRS += p_external',
-            'SUBSTITUTE_FILES += Makefile',
         ])
 
     def test_substitute_config_files(self):
         """Ensure substituted config files are produced."""
         env = self._consume('substitute_config_files', RecursiveMakeBackend)
 
         p = os.path.join(env.topobjdir, 'foo')
         self.assertTrue(os.path.exists(p))
@@ -130,17 +130,17 @@ class TestRecursiveMakeBackend(BackendTe
             'TEST = foo',
         ])
 
     def test_variable_passthru(self):
         """Ensure variable passthru is written out correctly."""
         env = self._consume('variable_passthru', RecursiveMakeBackend)
 
         backend_path = os.path.join(env.topobjdir, 'backend.mk')
-        lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:-1]]
+        lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
 
         expected = {
             'ASFILES': [
                 'ASFILES += bar.s',
                 'ASFILES += foo.asm',
             ],
             'XPIDL_FLAGS': [
                 'XPIDL_FLAGS += -Idir1',
@@ -162,17 +162,17 @@ class TestRecursiveMakeBackend(BackendTe
             found = [str for str in lines if str.startswith(var)]
             self.assertEqual(found, val)
 
     def test_exports(self):
         """Ensure EXPORTS is written out correctly."""
         env = self._consume('exports', RecursiveMakeBackend)
 
         backend_path = os.path.join(env.topobjdir, 'backend.mk')
-        lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:-1]]
+        lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
 
         self.assertEqual(lines, [
             'MOZBUILD_DERIVED := 1',
             'NO_MAKEFILE_RULE := 1',
             'NO_SUBMAKEFILES_RULE := 1',
             'EXPORTS += foo.h',
             'EXPORTS_NAMESPACES += mozilla',
             'EXPORTS_mozilla += mozilla1.h mozilla2.h',
@@ -184,17 +184,17 @@ class TestRecursiveMakeBackend(BackendTe
             'EXPORTS_nspr/private += pprio.h',
         ])
 
     def test_xpcshell_manifests(self):
         """Ensure XPCSHELL_TESTS_MANIFESTS is written out correctly."""
         env = self._consume('xpcshell_manifests', RecursiveMakeBackend)
 
         backend_path = os.path.join(env.topobjdir, 'backend.mk')
-        lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:-1]]
+        lines = [l.strip() for l in open(backend_path, 'rt').readlines()[2:]]
 
         # Avoid positional parameter and async related breakage
         var = 'XPCSHELL_TESTS'
         xpclines = sorted([val for val in lines if val.startswith(var)])
 
         # Assignment[aa], append[cc], conditional[valid]
         expected = ('aa', 'bb', 'cc', 'dd', 'valid_val')
         self.assertEqual(xpclines, ["XPCSHELL_TESTS += %s" % val for val in expected])