Bug 579718 part B - packaging and build changes to ship a single chrome.manifest from which other manifests are read. r=khuey See long-form commit details below:
authorBenjamin Smedberg <benjamin@smedbergs.us>
Thu, 22 Jul 2010 10:38:59 -0400
changeset 49201 476bdda74315923804466207e18f7859ad813af5
parent 49200 fe8141e66e81f4107527af5b7afa2a7e7feed6b4
child 49202 d8ea7a749eecb9585c72376b6bffa91defbe8e96
push idunknown
push userunknown
push dateunknown
reviewerskhuey
bugs579718
milestone2.0b3pre
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 579718 part B - packaging and build changes to ship a single chrome.manifest from which other manifests are read. r=khuey See long-form commit details below: In a nonpackaged build, we have the following manifests: * chrome.manifest is just a series of manifest directives for all the other manifests, generated in rules.mk using buildlist.py ** components/interfaces.manifest is also generated in rules.mk using buildlist.py ** components/components.manifest contains only the *binary* components. JS components each have their own manifest which is installed to dist/bin/components ** Each chrome/jarfile.jar has a matching chrome/jarfile.manifest, and JarMaker.py inserts this filename into the root chrome.manifest by importing buildlist.py In a packaged build, we end up with the following manifests: * chrome.manifest lists the four manifests below ** components/interfaces.manifest ** components/components.manifest ** chrome/nonlocalized.manifest ** chrome/localized.manifest In the future it should be possible to integrate the three non-localized manifests directly into the root chrome.manifest for increased performance, but this is slightly tricky because it involves rewriting some manifest instructions which may include relative JAR URIs. That is left for a future followup. When we repackage a l10n build, we only have to replace chrome/localized.manifest.
browser/locales/Makefile.in
config/JarMaker.py
config/rules.mk
config/tests/unit-JarMaker.py
js/src/config/rules.mk
testing/mochitest/runtests.py.in
toolkit/locales/l10n.mk
toolkit/mozapps/installer/link-manifests.py
toolkit/mozapps/installer/packager.mk
xpinstall/packager/Packager.pm
xpinstall/packager/xptlink.pl
--- a/browser/locales/Makefile.in
+++ b/browser/locales/Makefile.in
@@ -202,16 +202,17 @@ repackage-win32-installer: $(WIN32_INSTA
 	$(NSINSTALL) -D l10n-stage
 	$(CYGWIN_WRAPPER) 7z x -ol10n-stage $(WIN32_INSTALLER_IN)
 	$(RM) -r l10n-stage/localized
 	$(RM) l10n-stage/setup.exe
 # copy xpi-stage over, but not install.rdf and chrome.manifest,
 # those are just for language packs
 	cp -r $(DIST)/xpi-stage/locale-$(AB_CD) l10n-stage/localized
 	$(RM) l10n-stage/localized/install.rdf l10n-stage/localized/chrome.manifest
+	mv l10n-stage/localized/chrome/$(AB_CD).manifest l10n-stage/localized/chrome/localized.manifest
 	$(MAKE) -C ../installer/windows CONFIG_DIR=l10ngen l10ngen/setup.exe l10ngen/7zSD.sfx
 	cp ../installer/windows/l10ngen/setup.exe l10n-stage
 	$(NSINSTALL) -D l10n-stage/localized/uninstall
 	cp ../installer/windows/l10ngen/helper.exe l10n-stage/localized/uninstall
 	rm -f app.7z
 	cd l10n-stage && \
 	  $(CYGWIN_WRAPPER) 7z a -r -t7z ../app.7z -mx -m0=BCJ2 -m1=LZMA:d24 -m2=LZMA:d19 -m3=LZMA:d19 -mb0:1 -mb0s1:2 -mb0s2:3
 	cat ../installer/windows/l10ngen/7zSD.sfx \
--- a/config/JarMaker.py
+++ b/config/JarMaker.py
@@ -47,18 +47,19 @@ import os.path
 import re
 import logging
 from time import localtime
 from optparse import OptionParser
 from MozZipFile import ZipFile
 from cStringIO import StringIO
 from datetime import datetime
 
-from utils import pushback_iter
+from utils import pushback_iter, lockFile
 from Preprocessor import Preprocessor
+from buildlist import addEntriesToListFile
 
 __all__ = ['JarMaker']
 
 class ZipEntry:
   '''Helper class for jar output.
 
   This class defines a simple file-like object for a zipfile.ZipEntry
   so that we can consecutively write to it and then close it.
@@ -159,54 +160,61 @@ class JarMaker(object):
       self.pp.do_include(inc)
     includesvalue = self.pp.out.getvalue()
     if includesvalue:
       logging.info("WARNING: Includes produce non-empty output")
     self.pp.out = None
     pass
 
   def finalizeJar(self, jarPath, chromebasepath, register,
-                   doZip=True):
+                  doZip=True):
     '''Helper method to write out the chrome registration entries to
     jarfile.manifest or chrome.manifest, or both.
 
     The actual file processing is done in updateManifest.
     '''
     # rewrite the manifest, if entries given
     if not register:
       return
+
+    chromeManifest = os.path.join(os.path.dirname(jarPath),
+                                  '..', 'chrome.manifest')
+
     if self.useJarfileManifest:
       self.updateManifest(jarPath + '.manifest', chromebasepath % '',
                           register)
+      addEntriesToListFile(chromeManifest, ['manifest chrome/%s.manifest' % (os.path.basename(jarPath),)])
     if self.useChromeManifest:
-      manifestPath = os.path.join(os.path.dirname(jarPath),
-                                  '..', 'chrome.manifest')
-      self.updateManifest(manifestPath, chromebasepath % 'chrome/',
+      self.updateManifest(chromeManifest, chromebasepath % 'chrome/',
                           register)
 
   def updateManifest(self, manifestPath, chromebasepath, register):
     '''updateManifest replaces the % in the chrome registration entries
     with the given chrome base path, and updates the given manifest file.
     '''
-    myregister = dict.fromkeys(map(lambda s: s.replace('%', chromebasepath),
-                                   register.iterkeys()))
-    manifestExists = os.path.isfile(manifestPath)
-    mode = (manifestExists and 'r+b') or 'wb'
-    mf = open(manifestPath, mode)
-    if manifestExists:
-      # import previous content into hash, ignoring empty ones and comments
-      imf = re.compile('(#.*)?$')
-      for l in re.split('[\r\n]+', mf.read()):
-        if imf.match(l):
-          continue
-        myregister[l] = None
-      mf.seek(0)
-    for k in myregister.iterkeys():
-      mf.write(k + os.linesep)
-    mf.close()
+    lock = lockFile(manifestPath + '.lck')
+    try:
+      myregister = dict.fromkeys(map(lambda s: s.replace('%', chromebasepath),
+                                     register.iterkeys()))
+      manifestExists = os.path.isfile(manifestPath)
+      mode = (manifestExists and 'r+b') or 'wb'
+      mf = open(manifestPath, mode)
+      if manifestExists:
+        # import previous content into hash, ignoring empty ones and comments
+        imf = re.compile('(#.*)?$')
+        for l in re.split('[\r\n]+', mf.read()):
+          if imf.match(l):
+            continue
+          myregister[l] = None
+        mf.seek(0)
+      for k in myregister.iterkeys():
+        mf.write(k + os.linesep)
+      mf.close()
+    finally:
+      lock = None
   
   def makeJar(self, infile=None,
                jardir='',
                sourcedirs=[], topsourcedir='', localedirs=None):
     '''makeJar is the main entry point to JarMaker.
 
     It takes the input file, the output directory, the source dirs and the
     top source dir as argument, and optionally the l10n dirs.
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -883,16 +883,17 @@ else
 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)
 ifndef NO_COMPONENTS_MANIFEST
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/chrome.manifest "manifest components/components.manifest"
 	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/components/components.manifest "binary-component $(SHARED_LIBRARY)"
 endif
 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
@@ -1724,16 +1725,17 @@ ifneq ($(XPIDL_MODULE).idl,$(strip $(XPI
 	$(XPIDL_LINK) $(XPIDL_GEN_DIR)/$(XPIDL_MODULE).xpt $(patsubst %.idl,$(XPIDL_GEN_DIR)/%.xpt,$(XPIDLSRCS))
 endif # XPIDL_MODULE.xpt != XPIDLSRCS
 
 libs:: $(XPIDL_GEN_DIR)/$(XPIDL_MODULE).xpt
 ifndef NO_DIST_INSTALL
 	$(INSTALL) $(IFLAGS1) $(XPIDL_GEN_DIR)/$(XPIDL_MODULE).xpt $(FINAL_TARGET)/components
 ifndef NO_INTERFACES_MANIFEST
 	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/components/interfaces.manifest "interfaces $(XPIDL_MODULE).xpt"
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/chrome.manifest "manifest components/interfaces.manifest"
 endif
 endif
 
 endif # NO_GEN_XPT
 
 GARBAGE_DIRS		+= $(XPIDL_GEN_DIR)
 
 endif # XPIDLSRCS
@@ -1839,17 +1841,22 @@ ifndef NO_DIST_INSTALL
 	$(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; \
 	done
 endif
+endif
 
+EXTRA_MANIFESTS = $(filter %.manifest,$(EXTRA_COMPONENTS) $(EXTRA_PP_COMPONENTS))
+ifneq (,$(EXTRA_MANIFESTS))
+libs::
+	$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/chrome.manifest $(patsubst %,"manifest components/%",$(notdir $(EXTRA_MANIFESTS)))
 endif
 
 ################################################################################
 # Copy each element of EXTRA_JS_MODULES to $(FINAL_TARGET)/modules
 ifdef EXTRA_JS_MODULES
 libs:: $(EXTRA_JS_MODULES)
 ifndef NO_DIST_INSTALL
 	$(INSTALL) $(IFLAGS1) $^ $(FINAL_TARGET)/modules
@@ -2280,16 +2287,18 @@ FREEZE_VARIABLES = \
   EXPORTS \
   XPIDLSRCS \
   DIRS \
   LIBRARY \
   MODULE \
   REQUIRES \
   SHORT_LIBNAME \
   TIERS \
+  EXTRA_COMPONENTS \
+  EXTRA_PP_COMPONENTS \
   $(NULL)
 
 $(foreach var,$(FREEZE_VARIABLES),$(eval $(var)_FROZEN := '$($(var))'))
 
 CHECK_FROZEN_VARIABLES = $(foreach var,$(FREEZE_VARIABLES), \
   $(if $(subst $($(var)_FROZEN),,'$($(var))'),$(error Makefile variable '$(var)' changed value after including rules.mk. Was $($(var)_FROZEN), now $($(var)).)))
 
 libs export libs::
--- a/config/tests/unit-JarMaker.py
+++ b/config/tests/unit-JarMaker.py
@@ -38,17 +38,17 @@ class _TreeDiff(dircmp):
         if rv['funny']:
             chunks.append("%s don't compare" % ', '.join(rv['funny']))
         return '; '.join(chunks)
 
 class TestJarMaker(unittest.TestCase):
     """
     Unit tests for JarMaker.py
     """
-    debug = False # set to True to debug failing tests on disk
+    debug = True # set to True to debug failing tests on disk
     def setUp(self):
         self.tmpdir = mkdtemp()
         self.srcdir = os.path.join(self.tmpdir, 'src')
         os.mkdir(self.srcdir)
         self.builddir = os.path.join(self.tmpdir, 'build')
         os.mkdir(self.builddir)
         self.refdir = os.path.join(self.tmpdir, 'ref')
         os.mkdir(self.refdir)
@@ -137,16 +137,20 @@ class TestJarMaker(unittest.TestCase):
             open(jp, 'w').write('''ab-CD.jar:
 % locale app ab-CD %app
   app/''' + relpath + ' (%' + relpath + ''')
 ''')
             ldir = _mangle(relpath)
             os.mkdir(ldir)
             open(os.path.join(ldir, relpath), 'w').write(relpath+" content\n")
         # create reference
+        mf = open(os.path.join(self.refdir, 'chrome.manifest'), 'w')
+        mf.write('manifest chrome/ab-CD.manifest\n')
+        mf.close()
+
         chrome_ref = os.path.join(self.refdir, 'chrome')
         os.mkdir(chrome_ref)
         mf = open(os.path.join(chrome_ref, 'ab-CD.manifest'), 'wb')
         mf.write('locale app ab-CD jar:ab-CD.jar!/app\n')
         mf.close()
         ldir = os.path.join(chrome_ref, 'ab-CD.jar', 'app')
         os.makedirs(ldir)
         for relpath in ('foo', 'bar'):
--- a/js/src/config/rules.mk
+++ b/js/src/config/rules.mk
@@ -883,16 +883,17 @@ else
 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)
 ifndef NO_COMPONENTS_MANIFEST
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/chrome.manifest "manifest components/components.manifest"
 	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/components/components.manifest "binary-component $(SHARED_LIBRARY)"
 endif
 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
@@ -1724,16 +1725,17 @@ ifneq ($(XPIDL_MODULE).idl,$(strip $(XPI
 	$(XPIDL_LINK) $(XPIDL_GEN_DIR)/$(XPIDL_MODULE).xpt $(patsubst %.idl,$(XPIDL_GEN_DIR)/%.xpt,$(XPIDLSRCS))
 endif # XPIDL_MODULE.xpt != XPIDLSRCS
 
 libs:: $(XPIDL_GEN_DIR)/$(XPIDL_MODULE).xpt
 ifndef NO_DIST_INSTALL
 	$(INSTALL) $(IFLAGS1) $(XPIDL_GEN_DIR)/$(XPIDL_MODULE).xpt $(FINAL_TARGET)/components
 ifndef NO_INTERFACES_MANIFEST
 	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/components/interfaces.manifest "interfaces $(XPIDL_MODULE).xpt"
+	@$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/chrome.manifest "manifest components/interfaces.manifest"
 endif
 endif
 
 endif # NO_GEN_XPT
 
 GARBAGE_DIRS		+= $(XPIDL_GEN_DIR)
 
 endif # XPIDLSRCS
@@ -1839,17 +1841,22 @@ ifndef NO_DIST_INSTALL
 	$(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; \
 	done
 endif
+endif
 
+EXTRA_MANIFESTS = $(filter %.manifest,$(EXTRA_COMPONENTS) $(EXTRA_PP_COMPONENTS))
+ifneq (,$(EXTRA_MANIFESTS))
+libs::
+	$(PYTHON) $(MOZILLA_DIR)/config/buildlist.py $(FINAL_TARGET)/chrome.manifest $(patsubst %,"manifest components/%",$(notdir $(EXTRA_MANIFESTS)))
 endif
 
 ################################################################################
 # Copy each element of EXTRA_JS_MODULES to $(FINAL_TARGET)/modules
 ifdef EXTRA_JS_MODULES
 libs:: $(EXTRA_JS_MODULES)
 ifndef NO_DIST_INSTALL
 	$(INSTALL) $(IFLAGS1) $^ $(FINAL_TARGET)/modules
@@ -2280,16 +2287,18 @@ FREEZE_VARIABLES = \
   EXPORTS \
   XPIDLSRCS \
   DIRS \
   LIBRARY \
   MODULE \
   REQUIRES \
   SHORT_LIBNAME \
   TIERS \
+  EXTRA_COMPONENTS \
+  EXTRA_PP_COMPONENTS \
   $(NULL)
 
 $(foreach var,$(FREEZE_VARIABLES),$(eval $(var)_FROZEN := '$($(var))'))
 
 CHECK_FROZEN_VARIABLES = $(foreach var,$(FREEZE_VARIABLES), \
   $(if $(subst $($(var)_FROZEN),,'$($(var))'),$(error Makefile variable '$(var)' changed value after including rules.mk. Was $($(var)_FROZEN), now $($(var)).)))
 
 libs export libs::
--- a/testing/mochitest/runtests.py.in
+++ b/testing/mochitest/runtests.py.in
@@ -685,19 +685,20 @@ overlay chrome://browser/content/browser
       #Currently there are focus issues in chrome tests and issues with new windows and dialogs when using ipc
       manifestFile.write("overlay chrome://browser/content/browser.xul chrome://mochikit/content/ipc-overlay.xul")
       
     manifestFile.close()
 
     return self.installChromeFile(temp_file, options)
 
   def installChromeFile(self, filename, options):
-    (p, file) = os.path.split(filename)
     (path, leaf) = os.path.split(options.app)
-    manifest = os.path.join(path, "chrome", file)
+    manifestdir = os.path.join(path, "distribution", "bundles", "mochitest")
+    os.makedirs(manifestdir)
+    manifest = os.path.join(manifestdir, "chrome.manifest")
     shutil.copy(filename, manifest)
     return manifest
 
   def copyExtraFilesToProfile(self, options):
     "Copy extra files or dirs specified on the command line to the testing profile."
     for f in options.extraProfileFiles:
       abspath = self.getFullPath(f)
       dest = os.path.join(options.profilePath, os.path.basename(abspath))
--- a/toolkit/locales/l10n.mk
+++ b/toolkit/locales/l10n.mk
@@ -127,16 +127,17 @@ repackage-zip: ZIP_OUT="$(_ABS_DIST)/$(P
 repackage-zip: UNPACKAGE="$(ZIP_IN)"
 repackage-zip:
 # call a hook for apps to put their uninstall helper.exe into the package
 	$(UNINSTALLER_PACKAGE_HOOK)
 # copy xpi-stage over, but not install.rdf and chrome.manifest,
 # those are just for language packs
 	cd $(DIST)/xpi-stage/locale-$(AB_CD) && \
 	  tar --exclude=install.rdf --exclude=chrome.manifest $(TAR_CREATE_FLAGS) - * | ( cd $(STAGEDIST) && tar -xf - )
+	mv $(STAGEDIST)/chrome/$(AB_CD).manifest $(STAGEDIST)/chrome/localized.manifest
 ifneq (en,$(AB))
 ifeq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 	mv $(_ABS_DIST)/l10n-stage/$(MOZ_PKG_APPNAME)/$(_APPNAME)/Contents/Resources/en.lproj $(_ABS_DIST)/l10n-stage/$(MOZ_PKG_APPNAME)/$(_APPNAME)/Contents/Resources/$(AB).lproj
 endif
 endif
 	$(NSINSTALL) -D $(DIST)/l10n-stage/$(PKG_PATH)
 	cd $(DIST)/l10n-stage; \
 	  $(MAKE_PACKAGE)
--- a/toolkit/mozapps/installer/link-manifests.py
+++ b/toolkit/mozapps/installer/link-manifests.py
@@ -1,24 +1,20 @@
 import sys, os
 
-manifestsdir, distdir = sys.argv[1:]
+outmanifest = sys.argv[1]
+manifestdirs = sys.argv[2:]
 
-if not os.path.exists(manifestsdir):
-    print >>sys.stderr, "Warning: %s does not exist." % manifestsdir
-    sys.exit(0)
+outfd = open(outmanifest, 'w')
 
-for name in os.listdir(manifestsdir):
-    manifestdir = os.path.join(manifestsdir, name)
+for manifestdir in manifestdirs:
     if not os.path.isdir(manifestdir):
+        print >>sys.stderr, "Warning: trying to link manifests in missing directory '%s'" % manifestdir
         continue
 
-    manifestfile = os.path.join(distdir, 'components', name + '.manifest')
-    outfd = open(manifestfile, 'a')
-
     for name in os.listdir(manifestdir):
         infd = open(os.path.join(manifestdir, name))
         print >>outfd, "# %s" % name
         outfd.write(infd.read())
         print >>outfd
         infd.close()
 
-    outfd.close()
+outfd.close()
--- a/toolkit/mozapps/installer/packager.mk
+++ b/toolkit/mozapps/installer/packager.mk
@@ -359,35 +359,52 @@ endif
 		$(foreach pkg,$(MOZ_LOCALIZED_PKG_LIST),$(PKG_ARG)) )
 ifdef MOZ_OPTIONAL_PKG_LIST
 	$(call PACKAGER_COPY, "$(DIST)",\
 		"$(DEPTH)/installer-stage/optional", \
 		"$(MOZ_PKG_MANIFEST)", "$(PKGCP_OS)", 1, 0, 1 \
 		$(foreach pkg,$(MOZ_OPTIONAL_PKG_LIST),$(PKG_ARG)) )
 endif
 	$(PERL) $(MOZILLA_DIR)/xpinstall/packager/xptlink.pl -s $(DIST) -d $(DIST)/xpt -f $(DEPTH)/installer-stage/nonlocalized/components -v -x "$(XPIDL_LINK)"
-	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/link-manifests.py $(DIST)/manifests $(DEPTH)/installer-stage/nonlocalized
-
+	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/link-manifests.py \
+	  $(DEPTH)/installer-stage/nonlocalized/components/components.manifest \
+	  $(patsubst %,$(DIST)/manifests/%/components,$(MOZ_NONLOCALIZED_PKG_LIST))
+	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/link-manifests.py \
+	  $(DEPTH)/installer-stage/nonlocalized/chrome/nonlocalized.manifest \
+	  $(patsubst %,$(DIST)/manifests/%/chrome,$(MOZ_NONLOCALIZED_PKG_LIST))
+	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/link-manifests.py \
+	  $(DEPTH)/installer-stage/localized/chrome/localized.manifest \
+	  $(patsubst %,$(DIST)/manifests/%/chrome,$(MOZ_LOCALIZED_PKG_LIST))
+	printf "manifest components/interfaces.manifest\nmanifest components/components.manifest\nmanifest chrome/nonlocalized.manifest\nmanifest chrome/localized.manifest\n" > $(DEPTH)/installer-stage/nonlocalized/chrome.manifest
 
 stage-package: $(MOZ_PKG_MANIFEST) $(MOZ_PKG_REMOVALS_GEN)
 	@rm -rf $(DIST)/$(MOZ_PKG_DIR) $(DIST)/$(PKG_PATH)$(PKG_BASENAME).tar $(DIST)/$(PKG_PATH)$(PKG_BASENAME).dmg $@ $(EXCLUDE_LIST)
 # NOTE: this must be a tar now that dist links into the tree so that we
 # do not strip the binaries actually in the tree.
 	@echo "Creating package directory..."
 	@mkdir $(DIST)/$(MOZ_PKG_DIR)
 ifndef UNIVERSAL_BINARY
 # If UNIVERSAL_BINARY, the package will be made from an already-prepared
 # STAGEPATH
 ifdef MOZ_PKG_MANIFEST
 	$(RM) -rf $(DIST)/xpt $(RM) -rf $(DIST)/manifests
 	$(call PACKAGER_COPY, "$(call core_abspath,$(DIST))",\
 		 "$(call core_abspath,$(DIST)/$(MOZ_PKG_DIR))", \
 		"$(MOZ_PKG_MANIFEST)", "$(PKGCP_OS)", 1, 0, 1)
 	$(PERL) $(MOZILLA_DIR)/xpinstall/packager/xptlink.pl -s $(DIST) -d $(DIST)/xpt -f $(DIST)/$(MOZ_PKG_DIR)/$(_BINPATH)/components -v -x "$(XPIDL_LINK)"
-	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/link-manifests.py $(DIST)/manifests $(DIST)/$(MOZ_PKG_DIR)/$(_BINPATH)
+	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/link-manifests.py \
+	  $(DIST)/$(MOZ_PKG_DIR)/$(_BINPATH)/components/components.manifest \
+	  $(patsubst %,$(DIST)/manifests/%/components,$(MOZ_NONLOCALIZED_PKG_LIST))
+	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/link-manifests.py \
+	  $(DIST)/$(MOZ_PKG_DIR)/$(_BINPATH)/chrome/nonlocalized.manifest \
+	  $(patsubst %,$(DIST)/manifests/%/chrome,$(MOZ_NONLOCALIZED_PKG_LIST))
+	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/link-manifests.py \
+	  $(DIST)/$(MOZ_PKG_DIR)/$(_BINPATH)/chrome/localized.manifest \
+	  $(patsubst %,$(DIST)/manifests/%/chrome,$(MOZ_LOCALIZED_PKG_LIST))
+	printf "manifest components/interfaces.manifest\nmanifest components/components.manifest\nmanifest chrome/nonlocalized.manifest\nmanifest chrome/localized.manifest\n" > $(DIST)/$(MOZ_PKG_DIR)/$(_BINPATH)/chrome.manifest
 else # !MOZ_PKG_MANIFEST
 ifeq ($(MOZ_PKG_FORMAT),DMG)
 ifndef STAGE_SDK
 	@cd $(DIST) && rsync -auv --copy-unsafe-links $(_APPNAME) $(MOZ_PKG_DIR)
 	@echo "Linking XPT files..."
 	@rm -rf $(DIST)/xpt
 	@$(NSINSTALL) -D $(DIST)/xpt
 	@($(XPIDL_LINK) $(DIST)/xpt/$(MOZ_PKG_APPNAME).xpt $(DIST)/$(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)/components/*.xpt && rm -f $(DIST)/$(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)/components/*.xpt && cp $(DIST)/xpt/$(MOZ_PKG_APPNAME).xpt $(DIST)/$(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)/components && printf "interfaces $(MOZ_PKG_APPNAME).xpt\n" >$(DIST)/$(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)/components/interfaces.manifest) || echo No *.xpt files found in: $(DIST)/$(STAGEPATH)$(MOZ_PKG_DIR)$(_BINPATH)/components/.  Continuing...
--- a/xpinstall/packager/Packager.pm
+++ b/xpinstall/packager/Packager.pm
@@ -225,22 +225,23 @@ sub do_copyfile
     return;
   }
   else {
     ($debug >= 10) && print " '$srcpath$srcname$srcsuffix' is not a directory\n";
   }
 
   # set the destination path, if alternate destination given, use it.
   if ($flat) {
-    if ($srcsuffix eq ".manifest" && $srcpath =~ m|/components/$|) {
+    if ($srcsuffix eq ".manifest" && $srcpath =~ m'/(chrome|components)/$') {
+      my $subdir = $1;
       if ($component eq "") {
         die ("Manifest file was not part of a component.");
       }
 
-      $destpathcomp = "$srcdir/manifests/$component";
+      $destpathcomp = "$srcdir/manifests/$component/$subdir";
       $altdest = "$srcname$srcsuffix";
     }
     elsif ($srcsuffix eq ".xpt" && $srcpath =~ m|/components/$|) {
       if ($component eq "") {
         die ("XPT file was not part of a component.");
       }
 
       $destpathcomp = "$srcdir/xpt/$component/components";
--- a/xpinstall/packager/xptlink.pl
+++ b/xpinstall/packager/xptlink.pl
@@ -62,22 +62,21 @@ use Getopt::Long;
       "final|f=s",            \$finaldir,
 			"help|h",               \$help,
 			"debug=i",              \$debug,
 			"verbose|v",            \$verbose,
 			"xptlink|x=s",          \$xptlink,
 			"<>",                   \&do_badargument
 			);
 
-if ($finaldir ne "") {
-  $bindir = "";
+if ($finaldir eq "") {
+  die "Error: -f is required";
 }
-else {
-  $bindir = "bin/";
-}
+
+my $bindir = "";
 
 # remove extra slashes from $destdir
 $destdir =~ s:/+:/:g;
 
 # set debug level
 if ($verbose && !($debug)) {
 	$debug = 1;
 } elsif ($debug != 0) {
@@ -128,26 +127,19 @@ foreach my $component (@xptdirs) {
                             push @xptfiles, "$destdir/$component/$bindir"."components/$file";
 				($debug >= 8) && print "xptfiles:\t@xptfiles\n";
 			}
 		}
 		closedir (COMPDIR);
 
 		# merge .xpt files into one if we found any in the dir
 		if ( scalar(@xptfiles) ) {
-      my ($merged, $fmerged, $manifest);
-      if ($finaldir ne "") {
-        $merged = "$finaldir/$component.xpt";
-        $manifest = "$finaldir/$component.manifest";
-      }
-      else {
-        $fmerged = "$destdir/$component/$bindir"."components/$component.xpt";
-        $merged = $fmerged.".new";
-        $manifest = "$destdir/$component/$bindir"."components/$component.manifest";
-      }
+      my ($merged, $manifest);
+      $merged = "$finaldir/$component.xpt";
+      $manifest = "$finaldir/interfaces.manifest";
 
       my @realxptfiles;
       my $realmerged;
       if ($^O eq "cygwin") {
           @realxptfiles = map {my $file = `cygpath -t mixed $_`;
                                chomp $file;
                                $file} @xptfiles;
 	  $realmerged = `cygpath -t mixed $merged`;
@@ -161,33 +153,18 @@ foreach my $component (@xptdirs) {
       my $cmdline = "$xptlink $realmerged @realxptfiles";
 			($debug >= 4) && print "$cmdline\n";
 			system($cmdline) == 0 || die ("'$cmdline' failed");
 
       print "Manifest file: $manifest";
       open MANIFEST, '>>', $manifest;
       print MANIFEST "interfaces $component.xpt\n";
       close MANIFEST;
-
-      if ($finaldir eq "") {
-        # remove old .xpt files in the component directory.
-        ($debug >= 2) && print "Deleting individual xpt files.\n";
-        for my $file (@xptfiles) {
-          ($debug >= 4) && print "\t$file";
-          unlink ($file) ||
-              die "Couldn't unlink file $file.\n";
-          ($debug >= 4) && print "\t\tdeleted\n\n";
-        }
-
-        ($debug >= 2) && print "Renaming $merged as $fmerged\n";
-        rename ($merged, $fmerged) ||
-            die "Rename of '$merged' to '$fmerged' failed.\n";
-			}
 		}
-	}
+  }
 }
 ($debug >= 1) && print "Linking .xpt files completed.\n";
 
 exit (0);
 
 
 #
 # Check that arguments to script are valid.