diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py
index a4fb77357c68a7397607e02bfa8127a3a1b7ea9d..8feb6b97a917fcb841d2876e276211d9baa2c5e8 100644
--- a/indra/lib/python/indra/util/llmanifest.py
+++ b/indra/lib/python/indra/util/llmanifest.py
@@ -621,6 +621,23 @@ def expand_globs(self, src, dst):
             d = src_re.sub(d_template, s.replace('\\', '/'))
             yield os.path.normpath(s), os.path.normpath(d)
 
+    def path2basename(self, path, file):
+        """
+        It is a common idiom to write:
+        self.path(os.path.join(somedir, somefile), somefile)
+
+        So instead you can write:
+        self.path2basename(somedir, somefile)
+
+        Note that this is NOT the same as:
+        self.path(os.path.join(somedir, somefile))
+
+        which is the same as:
+        temppath = os.path.join(somedir, somefile)
+        self.path(temppath, temppath)
+        """
+        return self.path(os.path.join(path, file), file)
+
     def path(self, src, dst=None):
         sys.stdout.write("Processing %s => %s ... " % (src, dst))
         sys.stdout.flush()
@@ -666,6 +683,10 @@ def try_path(src):
 
         print "%d files" % count
 
+        # Let caller check whether we processed as many files as expected. In
+        # particular, let caller notice 0.
+        return count
+
     def do(self, *actions):
         self.actions = actions
         self.construct()
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index d1c952ac3bd275fdc410bffe2996327e46a4f902..8b1c278cb393a514ae25da241fd03dfd2a2731e3 100644
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -28,6 +28,7 @@
 """
 import sys
 import os.path
+import errno
 import re
 import tarfile
 import time
@@ -74,20 +75,20 @@ def construct(self):
                 # include the list of Lindens (if any)
                 #   see https://wiki.lindenlab.com/wiki/Generated_Linden_Credits
                 linden_names_path = os.getenv("LINDEN_CREDITS")
-                if linden_names_path :
+                if not linden_names_path :
+                    print "No 'LINDEN_CREDITS' specified in environment, using built-in list"
+                else:
                     try:
                         linden_file = open(linden_names_path,'r')
+                    except IOError:
+                        print "No Linden names found at '%s', using built-in list" % linden_names_path
+                    else:
                          # all names should be one line, but the join below also converts to a string
                         linden_names = ', '.join(linden_file.readlines())
                         self.put_in_file(linden_names, "lindens.txt")
                         linden_file.close()
                         print "Linden names extracted from '%s'" % linden_names_path
                         self.file_list.append([linden_names_path,self.dst_path_of("lindens.txt")])
-                    except IOError:
-                        print "No Linden names found at '%s', using built-in list" % linden_names_path
-                        pass
-                else :
-                    print "No 'LINDEN_CREDITS' specified in environment, using built-in list"
 
                 # ... and the entire windlight directory
                 self.path("windlight")
@@ -149,14 +150,9 @@ def construct(self):
             self.path("gpu_table.txt")
 
             # The summary.json file gets left in the base checkout dir by
-            # build.sh. It's only created for a build.sh build, therefore we
-            # have to check whether it exists.  :-P
-            summary_json = "summary.json"
-            summary_json_path = os.path.join(os.pardir, os.pardir, summary_json)
-            if os.path.exists(os.path.join(self.get_src_prefix(), summary_json_path)):
-                self.path(summary_json_path, summary_json)
-            else:
-                print "No %s" % os.path.join(self.get_src_prefix(), summary_json_path)
+            # build.sh. It's only created for a build.sh build.
+            if not self.path2basename(os.path.join(os.pardir, os.pardir), "summary.json"):
+                print "No summary.json file"
 
     def login_channel(self):
         """Channel reported for login and upgrade purposes ONLY;
@@ -327,13 +323,13 @@ def construct(self):
             self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe())
 
         # Plugin host application
-        self.path(os.path.join(os.pardir,
-                               'llplugin', 'slplugin', self.args['configuration'], "slplugin.exe"),
-                  "slplugin.exe")
+        self.path2basename(os.path.join(os.pardir,
+                                        'llplugin', 'slplugin', self.args['configuration']),
+                           "slplugin.exe")
         
         #self.disable_manifest_check()
 
-        self.path(src="../viewer_components/updater/scripts/windows/update_install.bat", dst="update_install.bat")
+        self.path2basename("../viewer_components/updater/scripts/windows", "update_install.bat")
         # Get shared libs from the shared libs staging directory
         if self.prefix(src=os.path.join(os.pardir, 'sharedlibs', self.args['configuration']),
                        dst=""):
@@ -367,9 +363,7 @@ def construct(self):
 
 
             # Get fmod dll, continue if missing
-            try:
-                self.path("fmod.dll")
-            except:
+            if not self.path("fmod.dll"):
                 print "Skipping fmod.dll"
 
             # For textures
@@ -710,85 +704,82 @@ def construct(self):
                 self.path("uk.lproj")
                 self.path("zh-Hans.lproj")
 
-                libdir = "../packages/lib/release"
-                dylibs = {}
+                def path_optional(src, dst):
+                    """
+                    For a number of our self.path() calls, not only do we want
+                    to deal with the absence of src, we also want to remember
+                    which were present. Return either an empty list (absent)
+                    or a list containing dst (present). Concatenate these
+                    return values to get a list of all libs that are present.
+                    """
+                    if self.path(src, dst):
+                        return [dst]
+                    print "Skipping %s" % dst
+                    return []
 
-                # Need to get the llcommon dll from any of the build directories as well
-                lib = "llcommon"
-                libfile = "lib%s.dylib" % lib
-                try:
-                    self.path(self.find_existing_file(os.path.join(os.pardir,
-                                                                    lib,
-                                                                    self.args['configuration'],
-                                                                    libfile),
-                                                      os.path.join(libdir, libfile)),
-                                                      dst=libfile)
-                except RuntimeError:
-                    print "Skipping %s" % libfile
-                    dylibs[lib] = False
-                else:
-                    dylibs[lib] = True
-
-                if dylibs["llcommon"]:
-                    for libfile in ("libapr-1.0.dylib",
-                                    "libaprutil-1.0.dylib",
-                                    "libexpat.1.5.2.dylib",
-                                    "libexception_handler.dylib",
-                                    "libGLOD.dylib",
-                                    "libcollada14dom.dylib"
-                                    ):
-                        self.path(os.path.join(libdir, libfile), libfile)
-
-                # SLVoice and vivox lols
-                for libfile in ('libsndfile.dylib', 'libvivoxoal.dylib', 'libortp.dylib', \
-                    'libvivoxsdk.dylib', 'libvivoxplatform.dylib', 'SLVoice') :
-                     self.path(os.path.join(libdir, libfile), libfile)
+                libdir = "../packages/lib/release"
+                # dylibs is a list of all the .dylib files we expect to need
+                # in our bundled sub-apps. For each of these we'll create a
+                # symlink from sub-app/Contents/Resources to the real .dylib.
+                # Need to get the llcommon dll from any of the build directories as well.
+                libfile = "libllcommon.dylib"
+                dylibs = path_optional(self.find_existing_file(os.path.join(os.pardir,
+                                                               "llcommon",
+                                                               self.args['configuration'],
+                                                               libfile),
+                                                               os.path.join(libdir, libfile)),
+                                       dst=libfile)
+
+                for libfile in (
+                                "libapr-1.0.dylib",
+                                "libaprutil-1.0.dylib",
+                                "libcollada14dom.dylib",
+                                "libexpat.1.5.2.dylib",
+                                "libexception_handler.dylib",
+                                "libGLOD.dylib",
+                                ):
+                    dylibs += path_optional(os.path.join(libdir, libfile), libfile)
+
+                # SLVoice and vivox lols, no symlinks needed
+                for libfile in (
+                                'libortp.dylib',
+                                'libsndfile.dylib',
+                                'libvivoxoal.dylib',
+                                'libvivoxsdk.dylib',
+                                'libvivoxplatform.dylib',
+                                'SLVoice',
+                                ):
+                     self.path2basename(libdir, libfile)
                 
-                try:
-                    # FMOD for sound
-                    self.path(self.args['configuration'] + "/libfmodwrapper.dylib", "libfmodwrapper.dylib")
-                except:
-                    print "Skipping FMOD - not found"
+                # FMOD for sound
+                libfile = "libfmodwrapper.dylib"
+                path_optional(os.path.join(self.args['configuration'], libfile), libfile)
                 
                 # our apps
-                self.path("../mac_crash_logger/" + self.args['configuration'] + "/mac-crash-logger.app", "mac-crash-logger.app")
-                self.path("../mac_updater/" + self.args['configuration'] + "/mac-updater.app", "mac-updater.app")
-
-                # plugin launcher
-                self.path("../llplugin/slplugin/" + self.args['configuration'] + "/SLPlugin.app", "SLPlugin.app")
-
-                # our apps dependencies on shared libs
-                if dylibs["llcommon"]:
-                    mac_crash_logger_res_path = self.dst_path_of("mac-crash-logger.app/Contents/Resources")
-                    mac_updater_res_path = self.dst_path_of("mac-updater.app/Contents/Resources")
-                    slplugin_res_path = self.dst_path_of("SLPlugin.app/Contents/Resources")
-                    for libfile in ("libllcommon.dylib",
-                                    "libapr-1.0.dylib",
-                                    "libaprutil-1.0.dylib",
-                                    "libexpat.1.5.2.dylib",
-                                    "libexception_handler.dylib",
-                                    "libGLOD.dylib",
-                                    "libcollada14dom.dylib"
-                                    ):
-                        target_lib = os.path.join('../../..', libfile)
-                        self.run_command("ln -sf %(target)r %(link)r" % 
-                                         {'target': target_lib,
-                                          'link' : os.path.join(mac_crash_logger_res_path, libfile)}
-                                         )
-                        self.run_command("ln -sf %(target)r %(link)r" % 
-                                         {'target': target_lib,
-                                          'link' : os.path.join(mac_updater_res_path, libfile)}
-                                         )
-                        self.run_command("ln -sf %(target)r %(link)r" % 
-                                         {'target': target_lib,
-                                          'link' : os.path.join(slplugin_res_path, libfile)}
-                                         )
+                for app_bld_dir, app in (("mac_crash_logger", "mac-crash-logger.app"),
+                                         ("mac_updater", "mac-updater.app"),
+                                         # plugin launcher
+                                         (os.path.join("llplugin", "slplugin"), "SLPlugin.app"),
+                                         ):
+                    self.path2basename(os.path.join(os.pardir,
+                                                    app_bld_dir, self.args['configuration']),
+                                       app)
+
+                    # our apps dependencies on shared libs
+                    # for each app, for each dylib we collected in dylibs,
+                    # create a symlink to the real copy of the dylib.
+                    resource_path = self.dst_path_of(os.path.join(app, "Contents", "Resources"))
+                    for libfile in dylibs:
+                        symlinkf(os.path.join(os.pardir, os.pardir, os.pardir, libfile),
+                                 os.path.join(resource_path, libfile))
 
                 # plugins
                 if self.prefix(src="", dst="llplugin"):
-                    self.path("../media_plugins/quicktime/" + self.args['configuration'] + "/media_plugin_quicktime.dylib", "media_plugin_quicktime.dylib")
-                    self.path("../media_plugins/webkit/" + self.args['configuration'] + "/media_plugin_webkit.dylib", "media_plugin_webkit.dylib")
-                    self.path("../packages/lib/release/libllqtwebkit.dylib", "libllqtwebkit.dylib")
+                    self.path2basename("../media_plugins/quicktime/" + self.args['configuration'],
+                                       "media_plugin_quicktime.dylib")
+                    self.path2basename("../media_plugins/webkit/" + self.args['configuration'],
+                                       "media_plugin_webkit.dylib")
+                    self.path2basename("../packages/lib/release", "libllqtwebkit.dylib")
 
                     self.end_prefix("llplugin")
 
@@ -931,20 +922,25 @@ def construct(self):
             self.path("client-readme-voice.txt","README-linux-voice.txt")
             self.path("client-readme-joystick.txt","README-linux-joystick.txt")
             self.path("wrapper.sh","secondlife")
-            self.path("handle_secondlifeprotocol.sh", "etc/handle_secondlifeprotocol.sh")
-            self.path("register_secondlifeprotocol.sh", "etc/register_secondlifeprotocol.sh")
-            self.path("refresh_desktop_app_entry.sh", "etc/refresh_desktop_app_entry.sh")
-            self.path("launch_url.sh","etc/launch_url.sh")
+            if self.prefix(src="", dst="etc"):
+                self.path("handle_secondlifeprotocol.sh")
+                self.path("register_secondlifeprotocol.sh")
+                self.path("refresh_desktop_app_entry.sh")
+                self.path("launch_url.sh")
+                self.end_prefix("etc")
             self.path("install.sh")
             self.end_prefix("linux_tools")
 
         # Create an appropriate gridargs.dat for this package, denoting required grid.
         self.put_in_file(self.flags_list(), 'etc/gridargs.dat')
 
-        self.path("secondlife-bin","bin/do-not-directly-run-secondlife-bin")
-        self.path("../linux_crash_logger/linux-crash-logger","bin/linux-crash-logger.bin")
-        self.path("../linux_updater/linux-updater", "bin/linux-updater.bin")
-        self.path("../llplugin/slplugin/SLPlugin", "bin/SLPlugin")
+        if self.prefix(src="", dst="bin"):
+            self.path("secondlife-bin","do-not-directly-run-secondlife-bin")
+            self.path("../linux_crash_logger/linux-crash-logger","linux-crash-logger.bin")
+            self.path("../linux_updater/linux-updater", "linux-updater.bin")
+            self.path2basename("../llplugin/slplugin", "SLPlugin")
+            self.path2basename("../viewer_components/updater/scripts/linux", "update_install")
+            self.end_prefix("bin")
 
         if self.prefix("res-sdl"):
             self.path("*")
@@ -960,17 +956,13 @@ def construct(self):
                 self.end_prefix("res-sdl")
             self.end_prefix(icon_path)
 
-        self.path("../viewer_components/updater/scripts/linux/update_install", "bin/update_install")
-
         # plugins
         if self.prefix(src="", dst="bin/llplugin"):
-            self.path("../media_plugins/webkit/libmedia_plugin_webkit.so", "libmedia_plugin_webkit.so")
+            self.path2basename("../media_plugins/webkit", "libmedia_plugin_webkit.so")
             self.path("../media_plugins/gstreamer010/libmedia_plugin_gstreamer010.so", "libmedia_plugin_gstreamer.so")
             self.end_prefix("bin/llplugin")
 
-        try:
-            self.path("../llcommon/libllcommon.so", "lib/libllcommon.so")
-        except:
+        if not self.path("../llcommon/libllcommon.so", "lib/libllcommon.so"):
             print "Skipping llcommon.so (assuming llcommon was linked statically)"
 
         self.path("featuretable_linux.txt")
@@ -1054,11 +1046,8 @@ def construct(self):
             self.path("libopenjpeg.so*")
             self.path("libdirectfb-1.4.so.5")
             self.path("libfusion-1.4.so.5")
-            self.path("libdirect-1.4.so.5.0.4")
-            self.path("libdirect-1.4.so.5")
-            self.path("libhunspell-1.3.so")
-            self.path("libhunspell-1.3.so.0")
-            self.path("libhunspell-1.3.so.0.0.0")
+            self.path("libdirect-1.4.so.5*")
+            self.path("libhunspell-1.3.so*")
             self.path("libalut.so")
             self.path("libopenal.so", "libopenal.so.1")
             self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname
@@ -1082,12 +1071,8 @@ def construct(self):
             # version number.
             self.path("libfontconfig.so.*.*")
             self.path("libtcmalloc.so*") #formerly called google perf tools
-            try:
-                    self.path("libfmod-3.75.so")
-                    pass
-            except:
-                    print "Skipping libfmod-3.75.so - not found"
-                    pass
+            if not self.path("libfmod-3.75.so"):
+                print "Skipping libfmod-3.75.so - not found"
             self.end_prefix("lib")
 
             # Vivox runtimes
@@ -1116,5 +1101,25 @@ def construct(self):
 
 ################################################################
 
+def symlinkf(src, dst):
+    """
+    Like ln -sf, but uses os.symlink() instead of running ln.
+    """
+    try:
+        os.symlink(src, dst)
+    except OSError, err:
+        if err.errno != errno.EEXIST:
+            raise
+        # We could just blithely attempt to remove and recreate the target
+        # file, but that strategy doesn't work so well if we don't have
+        # permissions to remove it. Check to see if it's already the
+        # symlink we want, which is the usual reason for EEXIST.
+        if not (os.path.islink(dst) and os.readlink(dst) == src):
+            # Here either dst isn't a symlink or it's the wrong symlink.
+            # Remove and recreate. Caller will just have to deal with any
+            # exceptions at this stage.
+            os.remove(dst)
+            os.symlink(src, dst)
+
 if __name__ == "__main__":
     main()