bug 531135 - rewrite build-list.pl in Python to fix race conditions with locking. r=bsmedberg
authorTed Mielczarek <ted.mielczarek@gmail.com>
Mon, 14 Dec 2009 06:55:40 -0500
changeset 35724 83b704c74e5f0004b7fd629a4fed31d421110bf0
parent 35723 2469995038366a4a0a489324340e9020eb3c9b20
child 35725 ceef8a5c3ca1906d8f0a5bcb5469c8f451a8fc9b
push idunknown
push userunknown
push dateunknown
reviewersbsmedberg
bugs531135
milestone1.9.3a1pre
bug 531135 - rewrite build-list.pl in Python to fix race conditions with locking. r=bsmedberg
config/Makefile.in
config/build-list.pl
config/buildlist.py
config/rules.mk
config/tests/unit-buildlist.py
js/src/config/rules.mk
layout/tools/reftest/Makefile.in
toolkit/xre/Makefile.in
--- a/config/Makefile.in
+++ b/config/Makefile.in
@@ -158,16 +158,17 @@ clean clobber realclean clobber_all::
 endif
 
 PYUNITS := \
   unit-Expression.py \
   unit-Preprocessor.py \
   unit-nsinstall.py \
   unit-printprereleasesuffix.py \
   unit-JarMaker.py \
+  unit-buildlist.py \
   $(NULL)
 
 check:: check-python-modules check-jar-mn
 
 check-python-modules::
 	@$(EXIT_ON_ERROR) \
 	for test in $(PYUNITS); do \
 	  $(PYTHON) $(srcdir)/tests/$$test ; \
deleted file mode 100755
--- a/config/build-list.pl
+++ /dev/null
@@ -1,114 +0,0 @@
-#!env perl
-
-#
-# ***** BEGIN LICENSE BLOCK *****
-# Version: MPL 1.1/GPL 2.0/LGPL 2.1
-#
-# The contents of this file are subject to the Mozilla Public License Version
-# 1.1 (the "License"); you may not use this file except in compliance with
-# the License. You may obtain a copy of the License at
-# http://www.mozilla.org/MPL/
-#
-# Software distributed under the License is distributed on an "AS IS" basis,
-# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
-# for the specific language governing rights and limitations under the
-# License.
-#
-# The Original Code is mozilla.org code.
-#
-# The Initial Developer of the Original Code is
-# Netscape Communications Corporation.
-# Portions created by the Initial Developer are Copyright (C) 2001
-# the Initial Developer. All Rights Reserved.
-#
-# Contributor(s):
-#   Christopher Seawood <cls@seawood.org>
-#
-# Alternatively, the contents of this file may be used under the terms of
-# either of the GNU General Public License Version 2 or later (the "GPL"),
-# or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
-# in which case the provisions of the GPL or the LGPL are applicable instead
-# of those above. If you wish to allow use of your version of this file only
-# under the terms of either the GPL or the LGPL, and not to allow others to
-# use your version of this file under the terms of the MPL, indicate your
-# decision by deleting the provisions above and replace them with the notice
-# and other provisions required by the GPL or the LGPL. If you do not delete
-# the provisions above, a recipient may use your version of this file under
-# the terms of any one of the MPL, the GPL or the LGPL.
-#
-# ***** END LICENSE BLOCK *****
-
-#
-# A generic script to add entries to a file 
-# if the entry does not already exist
-# 
-# Usage: $0 [-l] <filename> <entry> [<entry> <entry>]
-#
-#   -l do not attempt flock the file.
-
-use Fcntl qw(:DEFAULT :flock);
-use Getopt::Std;
-use mozLock;
-
-sub usage() {
-    print "$0 [-l] <filename> <entry>\n";
-    exit(1);
-}
-
-$nofilelocks = 0;
-
-getopts("l");
-
-$nofilelocks = 1 if defined($::opt_l);
-
-$file = shift;
-
-undef @entrylist;
-while (defined($entry = shift)) {
-    push @entrylist, $entry;
-}
-
-$lockfile = $file . ".lck";
-
-# touch the file if it doesn't exist
-if ( ! -e "$file") {
-    $now = time;
-    utime $now, $now, $file;
-}
-
-# This needs to be atomic
-mozLock($lockfile) unless $nofilelocks;
-
-# Read entire file into mem
-undef @inbuf;
-if ( -e "$file" ) {
-    open(IN, "$file") || die ("$file: $!\n");
-    binmode(IN);
-    while ($tmp = <IN>) {
-	chomp($tmp);
-	push @inbuf, $tmp;
-    }
-    close(IN);
-}
-
-undef @outbuf;
-# Add each entry to file if it's not already there
-foreach $entry (@entrylist) {
-    push @outbuf, $entry if (!grep(/^$entry$/, @inbuf));
-}
-
-$count = $#outbuf + 1;
-
-# Append new entry to file
-if ($count) {
-    open(OUT, ">>$file") || die ("$file: $!\n");
-    binmode(OUT);
-    foreach $entry (@outbuf) {
-	print OUT "$entry\n";
-    }
-    close(OUT);
-}
-
-mozUnlock($lockfile) unless $nofilelocks;
-
-exit(0);
new file mode 100644
--- /dev/null
+++ b/config/buildlist.py
@@ -0,0 +1,73 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Mozilla build system.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#  Ted Mielczarek <ted.mielczarek@gmail.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisiwons above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+'''A generic script to add entries to a file 
+if the entry does not already exist.
+
+Usage: buildlist.py <filename> <entry> [<entry> ...]
+'''
+
+import sys
+import os
+from utils import lockFile
+
+def addEntriesToListFile(listFile, entries):
+  """Given a file |listFile| containing one entry per line,
+  add each entry in |entries| to the file, unless it is already
+  present."""
+  lock = lockFile(listFile + ".lck")
+  try:
+    if os.path.exists(listFile):
+      f = open(listFile)
+      existing = set([x.strip() for x in f.readlines()])
+      f.close()
+    else:
+      existing = set()
+    f = open(listFile, 'a')
+    for e in entries:
+      if e not in existing:
+        f.write("%s\n" % e)
+        existing.add(e)
+    f.close()
+  finally:
+    lock = None
+
+if __name__ == '__main__':
+  if len(sys.argv) < 3:
+    print >>sys.stderr, "Usage: buildlist.py <list file> <entry> [<entry> ...]"
+    sys.exit(1)
+  addEntriesToListFile(sys.argv[1], sys.argv[2:])
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -150,17 +150,17 @@ define _INSTALL_TESTS
 $(TEST_INSTALLER) $(wildcard $(srcdir)/$(dir)/*) $(testxpcobjdir)/$(MODULE)/$(dir)
 
 endef # do not remove the blank line!
 
 SOLO_FILE ?= $(error Specify a test filename in SOLO_FILE when using check-interactive or check-one)
 
 libs::
 	$(foreach dir,$(XPCSHELL_TESTS),$(_INSTALL_TESTS))
-	$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl \
+	$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py \
           $(testxpcobjdir)/all-test-dirs.list \
           $(addprefix $(MODULE)/,$(XPCSHELL_TESTS))
 
 testxpcsrcdir = $(topsrcdir)/testing/xpcshell
 
 # Execute all tests in the $(XPCSHELL_TESTS) directories.
 # See also testsuite-targets.mk 'xpcshell-tests' target for global execution.
 xpcshell-tests:
@@ -819,23 +819,23 @@ endif
 #
 # Rule to create list of libraries for final link
 #
 export::
 ifdef LIBRARY_NAME
 ifdef EXPORT_LIBRARY
 ifdef IS_COMPONENT
 ifdef BUILD_STATIC_LIBS
-	@$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_LINK_COMPS) $(STATIC_LIBRARY_NAME)
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_LINK_COMPS) $(STATIC_LIBRARY_NAME)
 ifdef MODULE_NAME
-	@$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_LINK_COMP_NAMES) $(MODULE_NAME)
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_LINK_COMP_NAMES) $(MODULE_NAME)
 endif
 endif # BUILD_STATIC_LIBS
 else  # !IS_COMPONENT
-	$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_LINK_LIBS) $(STATIC_LIBRARY_NAME)
+	$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_LINK_LIBS) $(STATIC_LIBRARY_NAME)
 endif # IS_COMPONENT
 endif # EXPORT_LIBRARY
 endif # LIBRARY_NAME
 
 # Create dependencies on static (and shared EXTRA_DSO_LIBS) libraries
 LIBS_DEPS = $(filter %.$(LIB_SUFFIX), $(LIBS))
 HOST_LIBS_DEPS = $(filter %.$(LIB_SUFFIX), $(HOST_LIBS))
 DSO_LDOPTS_DEPS = $(EXTRA_DSO_LIBS) $(filter %.$(LIB_SUFFIX), $(EXTRA_DSO_LDOPTS))
@@ -885,17 +885,17 @@ else
 	$(INSTALL) $(IFLAGS1) $(LIBRARY) $(DIST)/lib
 endif
 endif # DIST_INSTALL
 endif # LIBRARY
 ifdef SHARED_LIBRARY
 ifdef IS_COMPONENT
 	$(INSTALL) $(IFLAGS2) $(SHARED_LIBRARY) $(FINAL_TARGET)/components
 	$(ELF_DYNSTR_GC) $(FINAL_TARGET)/components/$(SHARED_LIBRARY)
-	@$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_TARGET)/components/components.list $(SHARED_LIBRARY)
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/components/components.list $(SHARED_LIBRARY)
 ifdef BEOS_ADDON_WORKAROUND
 	( cd $(FINAL_TARGET)/components && $(CC) -nostart -o $(SHARED_LIBRARY).stub $(SHARED_LIBRARY) )
 endif
 else # ! IS_COMPONENT
 ifneq (,$(filter OS2 WINNT WINCE,$(OS_ARCH)))
 	$(INSTALL) $(IFLAGS2) $(IMPORT_LIBRARY) $(DIST)/lib
 else
 	$(INSTALL) $(IFLAGS2) $(SHARED_LIBRARY) $(DIST)/lib
@@ -1806,32 +1806,32 @@ endif # XPIDLSRCS
 endif # MOZ_JAVAXPCOM
 
 ################################################################################
 # Copy each element of EXTRA_COMPONENTS to $(FINAL_TARGET)/components
 ifdef EXTRA_COMPONENTS
 libs:: $(EXTRA_COMPONENTS)
 ifndef NO_DIST_INSTALL
 	$(INSTALL) $(IFLAGS1) $^ $(FINAL_TARGET)/components
-	@$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_TARGET)/components/components.list $(notdir $^)
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/components/components.list $(notdir $^)
 endif
 
 endif
 
 ifdef EXTRA_PP_COMPONENTS
 libs:: $(EXTRA_PP_COMPONENTS)
 ifndef NO_DIST_INSTALL
 	$(EXIT_ON_ERROR) \
 	$(NSINSTALL) -D $(FINAL_TARGET)/components; \
 	for i in $^; do \
 	  fname=`basename $$i`; \
 	  dest=$(FINAL_TARGET)/components/$${fname}; \
 	  $(RM) -f $$dest; \
 	  $(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $(ACDEFINES) $(XULPPFLAGS) $$i > $$dest; \
-	  $(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_TARGET)/components/components.list $$fname; \
+	  $(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/components/components.list $$fname; \
 	done
 endif
 
 endif
 
 ################################################################################
 # Copy each element of EXTRA_JS_MODULES to $(FINAL_TARGET)/modules
 ifdef EXTRA_JS_MODULES
new file mode 100644
--- /dev/null
+++ b/config/tests/unit-buildlist.py
@@ -0,0 +1,80 @@
+import unittest
+
+import os, sys, os.path, time
+from tempfile import mkdtemp
+from shutil import rmtree
+sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
+
+from buildlist import addEntriesToListFile
+
+class TestBuildList(unittest.TestCase):
+  """
+  Unit tests for buildlist.py
+  """
+  def setUp(self):
+    self.tmpdir = mkdtemp()
+
+  def tearDown(self):
+    rmtree(self.tmpdir)
+
+  # utility methods for tests
+  def touch(self, file, dir=None):
+    if dir is None:
+      dir = self.tmpdir
+    f = os.path.join(dir, file)
+    open(f, 'w').close()
+    return f
+
+  def assertFileContains(self, filename, l):
+    """Assert that the lines in the file |filename| are equal
+    to the contents of the list |l|, in order."""
+    l = l[:]
+    f = open(filename, 'r')
+    lines = [line.rstrip() for line in f.readlines()]
+    f.close()
+    for line in lines:
+      self.assert_(len(l) > 0, "ran out of expected lines! (expected '%s', got '%s')" % (l, lines))
+      self.assertEqual(line, l.pop(0))
+    self.assert_(len(l) == 0, "not enough lines in file! (expected '%s', got '%s'" % (l, lines))
+
+  def test_basic(self):
+    "Test that addEntriesToListFile works when file doesn't exist."
+    testfile = os.path.join(self.tmpdir, "test.list")
+    l = ["a", "b", "c"]
+    addEntriesToListFile(testfile, l)
+    self.assertFileContains(testfile, l)
+    # ensure that attempting to add the same entries again doesn't change it
+    addEntriesToListFile(testfile, l)
+    self.assertFileContains(testfile, l)
+
+  def test_append(self):
+    "Test adding new entries."
+    testfile = os.path.join(self.tmpdir, "test.list")
+    l = ["a", "b", "c"]
+    addEntriesToListFile(testfile, l)
+    self.assertFileContains(testfile, l)
+    l2 = ["x","y","z"]
+    addEntriesToListFile(testfile, l2)
+    l.extend(l2)
+    self.assertFileContains(testfile, l)
+
+  def test_append_some(self):
+    "Test adding new entries mixed with existing entries."
+    testfile = os.path.join(self.tmpdir, "test.list")
+    l = ["a", "b", "c"]
+    addEntriesToListFile(testfile, l)
+    self.assertFileContains(testfile, l)
+    addEntriesToListFile(testfile, ["a", "x", "c", "z"])
+    self.assertFileContains(testfile, ["a", "b", "c", "x", "z"])
+
+  def test_add_multiple(self):
+    """Test that attempting to add the same entry multiple times results in
+    only one entry being added."""
+    testfile = os.path.join(self.tmpdir, "test.list")
+    addEntriesToListFile(testfile, ["a","b","a","a","b"])
+    self.assertFileContains(testfile, ["a","b"])
+    addEntriesToListFile(testfile, ["c","a","c","b","c"])
+    self.assertFileContains(testfile, ["a","b","c"])
+
+if __name__ == '__main__':
+  unittest.main()
--- a/js/src/config/rules.mk
+++ b/js/src/config/rules.mk
@@ -150,17 +150,17 @@ define _INSTALL_TESTS
 $(TEST_INSTALLER) $(wildcard $(srcdir)/$(dir)/*) $(testxpcobjdir)/$(MODULE)/$(dir)
 
 endef # do not remove the blank line!
 
 SOLO_FILE ?= $(error Specify a test filename in SOLO_FILE when using check-interactive or check-one)
 
 libs::
 	$(foreach dir,$(XPCSHELL_TESTS),$(_INSTALL_TESTS))
-	$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl \
+	$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py \
           $(testxpcobjdir)/all-test-dirs.list \
           $(addprefix $(MODULE)/,$(XPCSHELL_TESTS))
 
 testxpcsrcdir = $(topsrcdir)/testing/xpcshell
 
 # Execute all tests in the $(XPCSHELL_TESTS) directories.
 # See also testsuite-targets.mk 'xpcshell-tests' target for global execution.
 xpcshell-tests:
@@ -819,23 +819,23 @@ endif
 #
 # Rule to create list of libraries for final link
 #
 export::
 ifdef LIBRARY_NAME
 ifdef EXPORT_LIBRARY
 ifdef IS_COMPONENT
 ifdef BUILD_STATIC_LIBS
-	@$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_LINK_COMPS) $(STATIC_LIBRARY_NAME)
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_LINK_COMPS) $(STATIC_LIBRARY_NAME)
 ifdef MODULE_NAME
-	@$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_LINK_COMP_NAMES) $(MODULE_NAME)
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_LINK_COMP_NAMES) $(MODULE_NAME)
 endif
 endif # BUILD_STATIC_LIBS
 else  # !IS_COMPONENT
-	$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_LINK_LIBS) $(STATIC_LIBRARY_NAME)
+	$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_LINK_LIBS) $(STATIC_LIBRARY_NAME)
 endif # IS_COMPONENT
 endif # EXPORT_LIBRARY
 endif # LIBRARY_NAME
 
 # Create dependencies on static (and shared EXTRA_DSO_LIBS) libraries
 LIBS_DEPS = $(filter %.$(LIB_SUFFIX), $(LIBS))
 HOST_LIBS_DEPS = $(filter %.$(LIB_SUFFIX), $(HOST_LIBS))
 DSO_LDOPTS_DEPS = $(EXTRA_DSO_LIBS) $(filter %.$(LIB_SUFFIX), $(EXTRA_DSO_LDOPTS))
@@ -885,17 +885,17 @@ else
 	$(INSTALL) $(IFLAGS1) $(LIBRARY) $(DIST)/lib
 endif
 endif # DIST_INSTALL
 endif # LIBRARY
 ifdef SHARED_LIBRARY
 ifdef IS_COMPONENT
 	$(INSTALL) $(IFLAGS2) $(SHARED_LIBRARY) $(FINAL_TARGET)/components
 	$(ELF_DYNSTR_GC) $(FINAL_TARGET)/components/$(SHARED_LIBRARY)
-	@$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_TARGET)/components/components.list $(SHARED_LIBRARY)
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/components/components.list $(SHARED_LIBRARY)
 ifdef BEOS_ADDON_WORKAROUND
 	( cd $(FINAL_TARGET)/components && $(CC) -nostart -o $(SHARED_LIBRARY).stub $(SHARED_LIBRARY) )
 endif
 else # ! IS_COMPONENT
 ifneq (,$(filter OS2 WINNT WINCE,$(OS_ARCH)))
 	$(INSTALL) $(IFLAGS2) $(IMPORT_LIBRARY) $(DIST)/lib
 else
 	$(INSTALL) $(IFLAGS2) $(SHARED_LIBRARY) $(DIST)/lib
@@ -1806,32 +1806,32 @@ endif # XPIDLSRCS
 endif # MOZ_JAVAXPCOM
 
 ################################################################################
 # Copy each element of EXTRA_COMPONENTS to $(FINAL_TARGET)/components
 ifdef EXTRA_COMPONENTS
 libs:: $(EXTRA_COMPONENTS)
 ifndef NO_DIST_INSTALL
 	$(INSTALL) $(IFLAGS1) $^ $(FINAL_TARGET)/components
-	@$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_TARGET)/components/components.list $(notdir $^)
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/components/components.list $(notdir $^)
 endif
 
 endif
 
 ifdef EXTRA_PP_COMPONENTS
 libs:: $(EXTRA_PP_COMPONENTS)
 ifndef NO_DIST_INSTALL
 	$(EXIT_ON_ERROR) \
 	$(NSINSTALL) -D $(FINAL_TARGET)/components; \
 	for i in $^; do \
 	  fname=`basename $$i`; \
 	  dest=$(FINAL_TARGET)/components/$${fname}; \
 	  $(RM) -f $$dest; \
 	  $(PYTHON) $(topsrcdir)/config/Preprocessor.py $(DEFINES) $(ACDEFINES) $(XULPPFLAGS) $$i > $$dest; \
-	  $(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_TARGET)/components/components.list $$fname; \
+	  $(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/components/components.list $$fname; \
 	done
 endif
 
 endif
 
 ################################################################################
 # Copy each element of EXTRA_JS_MODULES to $(FINAL_TARGET)/modules
 ifdef EXTRA_JS_MODULES
--- a/layout/tools/reftest/Makefile.in
+++ b/layout/tools/reftest/Makefile.in
@@ -84,17 +84,17 @@ endif
 $(_HARNESS_FILES): $(_DEST_DIR)
 
 # copy harness and the reftest extension bits to $(_DEST_DIR)
 copy-harness: $(_HARNESS_FILES)
 	$(INSTALL) $(_HARNESS_FILES) $(_DEST_DIR)
 	(cd $(DIST)/xpi-stage && tar $(TAR_CREATE_FLAGS) - reftest) | (cd $(_DEST_DIR) && tar -xf -)
 	$(INSTALL) $(DIST)/bin/components/httpd.js $(_DEST_DIR)/reftest/components
 # need to get httpd.js into components.list so it loads
-	@$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(_DEST_DIR)/reftest/components/components.list httpd.js
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(_DEST_DIR)/reftest/components/components.list httpd.js
 	$(INSTALL) $(DIST)/bin/components/test_necko.xpt $(_DEST_DIR)/reftest/components
 
 PKG_STAGE = $(DIST)/test-package-stage
 
 # stage harness and tests for packaging
 stage-package:
 	$(NSINSTALL) -D $(PKG_STAGE)/reftest && $(NSINSTALL) -D $(PKG_STAGE)/reftest/tests
 	@(cd $(DEPTH)/_tests/reftest/ && tar $(TAR_CREATE_FLAGS) - *) | (cd $(PKG_STAGE)/reftest && tar -xf -)
--- a/toolkit/xre/Makefile.in
+++ b/toolkit/xre/Makefile.in
@@ -171,20 +171,17 @@ endif
 ifdef ENABLE_TESTS
 DIRS += test
 endif
 
 include $(topsrcdir)/config/rules.mk
 
 ifdef BUILD_STATIC_LIBS
 export::
-	@$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_LINK_COMP_NAMES) Apprunner
-#	embedding/browser/gtk/src/Makefile.in sucks! we need to add an empty line to 
-# FINAL_LINK_COMPS to keep the two lists in sync :-(
-	@$(PERL) -I$(MOZILLA_DIR)/config $(MOZILLA_DIR)/config/build-list.pl $(FINAL_LINK_COMPS) ""
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_LINK_COMP_NAMES) Apprunner
 endif
 
 LOCAL_INCLUDES += \
 	-I$(srcdir) \
 	-I$(srcdir)/../profile/src \
 	-I$(topsrcdir)/config \
 	$(NULL)