From b7b4135e5507fd7be5aab24d5de64675dd28caca Mon Sep 17 00:00:00 2001
From: Ryan Williams <rdw@lindenlab.com>
Date: Wed, 21 Feb 2007 23:57:35 +0000
Subject: [PATCH] SL-33966: Viewer installed files are not described in a
 centralized source

This introduces the viewer_manifest.py and associated libraries.  Check out https://osiris.lindenlab.com/mediawiki/index.php/Installer_Manifest for some more information.

The gist of it is that all files to be included in the viewer installations are described in viewer_manifest.py.  viewer_manifest.py also acts as a packager, if you give it the right options.  All of the old methods of packaging (Makefile_Mac, scons, and the various .bat files) still work, but they are mostly just shells for calls to viewer_manifest.py.

You're now required to have python 2.3 or later on your machine to be able to package up an installer, but thankfully most people already have this.
---
 .../installers/windows/installer_template.nsi | 858 ++++++++++++++++++
 indra/newview/viewer_manifest.py              | 437 +++++++++
 indra/test/test_llmanifest.py                 | 109 +++
 3 files changed, 1404 insertions(+)
 create mode 100644 indra/newview/installers/windows/installer_template.nsi
 create mode 100755 indra/newview/viewer_manifest.py
 create mode 100644 indra/test/test_llmanifest.py

diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi
new file mode 100644
index 00000000000..6178cfbc965
--- /dev/null
+++ b/indra/newview/installers/windows/installer_template.nsi
@@ -0,0 +1,858 @@
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; secondlife setup.nsi
+;;; Copyright 2004-2006, Linden Research, Inc.
+;;; For info, see http://www.nullsoft.com/free/nsis/
+;;;
+;;; NSIS 2.02 or higher required
+;;; Author: James Cook, Don Kjer
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Compiler flags
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+;;; Detect NSIS compiler version
+!define "NSIS${NSIS_VERSION}"
+!ifdef "NSISv2.02" | "NSISv2.03" | "NSISv2.04" | "NSISv2.05" | "NSISv2.06"
+    ;;; before 2.07 defaulted lzma to solid (whole file)
+    SetCompressor lzma
+!else
+    ;;; after 2.07 required /solid for whole file compression
+    SetCompressor /solid lzma
+!endif
+
+SetOverwrite on				; overwrite files
+SetCompress auto			; compress iff saves space
+SetDatablockOptimize off		; only saves us 0.1%, not worth it
+XPStyle on                              ; add an XP manifest to the installer
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Project flags
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+%%VERSION%%
+
+;;; Tweak for different servers/builds (this placeholder is replaced by viewer_manifest.py)
+%%GRID_VARS%%
+
+Name ${INSTNAME}
+
+SubCaption 0 " Setup"			; override "license agreement" text
+
+BrandingText " "				; bottom of window text
+Icon res\install_icon.ico		; our custom icon
+UninstallIcon res\uninstall_icon.ico    ; our custom icon
+WindowIcon on					; show our icon in left corner
+BGGradient off					; no big background window
+CRCCheck on						; make sure CRC is OK
+InstProgressFlags smooth colored		; new colored smooth look
+ShowInstDetails nevershow		; no details, no "show" button
+SetOverwrite on					; stomp files by default
+AutoCloseWindow true			; after all files install, close window
+
+!ifdef UPDATE
+LicenseText "This package will update Second Life to version ${VERSION_LONG}." "Next >"
+!else
+LicenseText "This package will install Second Life on your computer." "Next >"
+!endif
+
+LicenseData "releasenotes.txt"
+
+InstallDir "$PROGRAMFILES\${INSTNAME}"
+InstallDirRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\${INSTNAME}" ""
+!ifdef UPDATE
+DirText "Installation Directory" "Select the Second Life directory to update:"
+!else
+DirText "Installation Directory" "Select the directory to install Second Life in:"
+!endif
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Variables
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Var INSTPROG
+Var INSTEXE
+Var INSTFLAGS
+Var INSTSHORTCUT
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Sections
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+Section ""						; (default section)
+
+SetShellVarContext all			; install for all users (if you change this, change it in the uninstall as well)
+
+
+; Start with some default values.
+StrCpy $INSTFLAGS "${INSTFLAGS}"
+StrCpy $INSTPROG "${INSTNAME}"
+StrCpy $INSTEXE "${INSTEXE}"
+StrCpy $INSTSHORTCUT "${SHORTCUT}"
+
+IfSilent +2
+Goto NOT_SILENT
+  Call CheckStartupParams                 ; Figure out where, what and how to install.
+NOT_SILENT:
+Call CheckWindowsVersion		; warn if on Windows 98/ME
+Call CheckIfAdministrator		; Make sure the user can install/uninstall
+Call CheckIfAlreadyCurrent		; Make sure that we haven't already installed this version
+Call CloseSecondLife			; Make sure we're not running
+Call RemoveNSIS					; Check for old NSIS install to remove
+
+;;; Don't remove cache files during a regular install, removing the inventory cache on upgrades results in lots of damage to the servers.
+;Call RemoveCacheFiles			; Installing over removes potentially corrupted
+								; VFS and cache files.
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Files
+;;;
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; This placeholder is replaced by the complete list of all the files in the installer, by viewer_manifest.py
+%%INSTALL_FILES%%
+
+; If this is a silent update, we don't need to re-create these shortcuts or registry entries.
+IfSilent POST_INSTALL
+
+; Shortcuts in start menu
+CreateDirectory	"$SMPROGRAMS\$INSTSHORTCUT"
+SetOutPath "$INSTDIR"
+CreateShortCut	"$SMPROGRAMS\$INSTSHORTCUT\$INSTSHORTCUT.lnk" \
+				"$INSTDIR\$INSTEXE" "$INSTFLAGS"
+
+!ifdef MUSEUM
+CreateShortCut	"$SMPROGRAMS\$INSTSHORTCUT\$INSTSHORTCUT Museum.lnk" \
+
+				"$INSTDIR\$INSTEXE" "$INSTFLAGS -simple"
+CreateShortCut	"$SMPROGRAMS\$INSTSHORTCUT\$INSTSHORTCUT Museum Spanish.lnk" \
+
+				"$INSTDIR\$INSTEXE" "$INSTFLAGS -simple -spanish"
+!endif
+
+WriteINIStr		"$SMPROGRAMS\$INSTSHORTCUT\SL Create Trial Account.url" \
+				"InternetShortcut" "URL" \
+				"http://www.secondlife.com/registration/"
+WriteINIStr		"$SMPROGRAMS\$INSTSHORTCUT\SL Your Account.url" \
+				"InternetShortcut" "URL" \
+				"http://www.secondlife.com/account/"
+CreateShortCut	"$SMPROGRAMS\$INSTSHORTCUT\SL Release Notes.lnk" \
+				"$INSTDIR\releasenotes.txt"
+CreateShortCut	"$SMPROGRAMS\$INSTSHORTCUT\SL Scripting Language Help.lnk" \
+				"$INSTDIR\lsl_guide.html"
+CreateShortCut	"$SMPROGRAMS\$INSTSHORTCUT\Uninstall $INSTSHORTCUT.lnk" \
+				'"$INSTDIR\uninst.exe"' '/P="$INSTPROG"'
+
+; Other shortcuts
+SetOutPath "$INSTDIR"
+CreateShortCut "$DESKTOP\$INSTSHORTCUT.lnk" "$INSTDIR\$INSTEXE" "$INSTFLAGS"
+CreateShortCut "$INSTDIR\$INSTSHORTCUT.lnk" "$INSTDIR\$INSTEXE" "$INSTFLAGS"
+CreateShortCut "$INSTDIR\Uninstall $INSTSHORTCUT.lnk" \
+				'"$INSTDIR\uninst.exe"' '/P="$INSTPROG"'
+
+!ifdef MUSEUM
+CreateShortCut "$DESKTOP\$INSTSHORTCUT Museum.lnk" "$INSTDIR\$INSTEXE" "$INSTFLAGS -simple"
+
+CreateShortCut "$DESKTOP\$INSTSHORTCUT Museum Spanish.lnk" "$INSTDIR\$INSTEXE" "$INSTFLAGS -simple -spanish"
+
+CreateShortCut "$INSTDIR\$INSTSHORTCUT Museum.lnk" "$INSTDIR\$INSTEXE" "$INSTFLAGS -simple"
+
+CreateShortCut "$INSTDIR\$INSTSHORTCUT Museum Spanish.lnk" "$INSTDIR\$INSTEXE" "$INSTFLAGS -simple -spanish"
+
+!endif
+
+; Write registry
+WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "" "$INSTDIR"
+WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Version" "${VERSION_LONG}"
+WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Flags" "$INSTFLAGS"
+WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Shortcut" "$INSTSHORTCUT"
+WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Exe" "$INSTEXE"
+WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "DisplayName" "$INSTPROG (remove only)"
+WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "UninstallString" '"$INSTDIR\uninst.exe" /P="$INSTPROG"'
+
+; Write URL registry info
+WriteRegStr HKEY_CLASSES_ROOT "${URLNAME}" "(default)" "URL:Second Life"
+WriteRegStr HKEY_CLASSES_ROOT "${URLNAME}" "URL Protocol" ""
+WriteRegStr HKEY_CLASSES_ROOT "${URLNAME}\DefaultIcon" "" '"$INSTDIR\$INSTEXE"'
+WriteRegExpandStr HKEY_CLASSES_ROOT "${URLNAME}\shell\open\command" "" '"$INSTDIR\$INSTEXE" $INSTFLAGS -url "%1"'
+
+Goto WRITE_UNINST
+
+POST_INSTALL:
+; Run a post-executable script if necessary.
+Call PostInstallExe
+
+WRITE_UNINST:
+; write out uninstaller
+WriteUninstaller "$INSTDIR\uninst.exe"
+
+; end of default section
+SectionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; PostInstallExe
+; This just runs any post installation scripts.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function PostInstallExe
+push $0
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "PostInstallExe"
+  ;MessageBox MB_OK '$0'
+  ExecWait '$0'
+pop $0
+FunctionEnd
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; CheckStartupParameters
+; Sets INSTFLAGS, INSTPROG, and INSTEXE.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function CheckStartupParams
+push $0
+push $R0
+
+  ; Look for a registry entry with info about where to update.
+  Call GetProgramName
+  pop $R0
+  StrCpy $INSTPROG "$R0"
+  StrCpy $INSTEXE "$R0.exe"
+
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" ""
+  ; If key doesn't exist, skip install
+  IfErrors ABORT
+  StrCpy $INSTDIR "$0"
+
+  ; We now have a directory to install to.  Get the startup parameters and shortcut as well.
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Flags"
+  IfErrors +2
+  StrCpy $INSTFLAGS "$0"
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Shortcut"
+  IfErrors +2
+  StrCpy $INSTSHORTCUT "$0"
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Exe"
+  IfErrors +2
+  StrCpy $INSTEXE "$0"
+  Goto FINISHED
+
+ABORT:
+  MessageBox MB_OK "Could not find the program '$INSTPROG'. Silent update failed."
+  Quit
+
+FINISHED:
+  ;MessageBox MB_OK "INSTPROG: $INSTPROG, INSTEXE: $INSTEXE, INSTFLAGS: $INSTFLAGS"
+pop $R0
+pop $0
+FunctionEnd
+
+Function un.CheckStartupParams
+push $0
+push $R0
+
+  ; Look for a registry entry with info about where to update.
+  Call un.GetProgramName
+  pop $R0
+  StrCpy $INSTPROG "$R0"
+  StrCpy $INSTEXE "$R0.exe"
+
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" ""
+  ; If key doesn't exist, skip install
+  IfErrors ABORT
+  StrCpy $INSTDIR "$0"
+
+  ; We now have a directory to install to.  Get the startup parameters and shortcut as well.
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Flags"
+  IfErrors +2
+  StrCpy $INSTFLAGS "$0"
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Shortcut"
+  IfErrors +2
+  StrCpy $INSTSHORTCUT "$0"
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Exe"
+  IfErrors +2
+  StrCpy $INSTEXE "$0"
+  Goto FINISHED
+
+ABORT:
+  MessageBox MB_OK "Could not find the program '$INSTPROG'. Silent update failed."
+  Quit
+
+FINISHED:
+  ;MessageBox MB_OK "INSTPROG: $INSTPROG, INSTEXE: $INSTEXE, INSTFLAGS: $INSTFLAGS"
+pop $R0
+pop $0
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; After install completes, offer readme file
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function .onInstSuccess
+	MessageBox MB_YESNO \
+	"Start Second Life now?" /SD IDYES IDNO NoReadme
+		; Assumes SetOutPath $INSTDIR
+		Exec '"$INSTDIR\$INSTEXE" $INSTFLAGS'
+	NoReadme:
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Remove old NSIS version. Modifies no variables.
+; Does NOT delete the LindenWorld directory, or any
+; user files in that directory.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function RemoveNSIS
+  Push $0
+  ; Grab the installation directory of the old version
+  DetailPrint "Checking for old version..."
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" ""
+
+  ; If key doesn't exist, skip uninstall
+  IfErrors NO_NSIS
+
+  ; Clean up legacy beta shortcuts
+  Delete "$SMPROGRAMS\Second Life Beta.lnk"
+  Delete "$DESKTOP\Second Life Beta.lnk"
+  Delete "$SMPROGRAMS\Second Life.lnk"
+  
+  ; Clean up old newview.exe file
+  Delete "$INSTDIR\newview.exe"
+
+  ; Intentionally don't delete the stuff in
+  ; Documents and Settings, so we keep the user's settings
+
+  NO_NSIS:
+  Pop $0
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Make sure we're not on Windows 98 / ME
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function CheckWindowsVersion
+	DetailPrint "Checking Windows version..."
+	Call GetWindowsVersion
+	Pop $R0
+	; Just get first two characters, ignore 4.0 part of "NT 4.0"
+	StrCpy $R0 $R0 2
+	; Blacklist certain OS versions
+	StrCmp $R0 "95" win_ver_bad
+	StrCmp $R0 "98" win_ver_bad
+	StrCmp $R0 "ME" win_ver_bad
+	StrCmp $R0 "NT" win_ver_bad
+	Return
+win_ver_bad:
+	MessageBox MB_YESNO 'Second Life only supports Windows XP, Windows 2000, and Mac OS X.$\n$\nAttempting to install on Windows $R0 can result in crashes and data loss.$\n$\nInstall anyway?' IDNO win_ver_abort
+	Return
+win_ver_abort:
+	Quit
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Make sure the user can install/uninstall
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function CheckIfAdministrator
+		DetailPrint "Checking for permission to install..."
+         UserInfo::GetAccountType
+         Pop $R0
+         StrCmp $R0 "Admin" is_admin
+         MessageBox MB_OK 'You appear to be using a "limited" account.$\nYou must be an "administrator" to install Second Life.'
+         Quit
+is_admin:
+         Return
+FunctionEnd
+
+Function un.CheckIfAdministrator
+		DetailPrint "Checking for permission to uninstall..."
+         UserInfo::GetAccountType
+         Pop $R0
+         StrCmp $R0 "Admin" is_admin
+         MessageBox MB_OK 'You appear to be using a "limited" account.$\nYou must be an "administrator" to uninstall Second Life.'
+         Quit
+is_admin:
+         Return
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Checks to see if the current version has already been installed (according to the registry).
+; If it has, allow user to bail out of install process.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function CheckIfAlreadyCurrent
+  Push $0
+	ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Version"
+    StrCmp $0 ${VERSION_LONG} 0 DONE
+	MessageBox MB_OKCANCEL "It appears that Second Life ${VERSION_LONG} is already installed.$\n$\nWould you like to install it again?" /SD IDOK IDOK DONE
+    Quit
+
+  DONE:
+    Pop $0
+    Return
+FunctionEnd
+	
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Close the program, if running. Modifies no variables.
+; Allows user to bail out of install process.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function CloseSecondLife
+  Push $0
+  FindWindow $0 "Second Life" ""
+  IntCmp $0 0 DONE
+  MessageBox MB_OKCANCEL "Second Life can't be installed while it is already running.$\n$\nFinish what you're doing then select OK to close Second Life and continue.$\nSelect CANCEL to cancel installation." IDOK CLOSE IDCANCEL CANCEL_INSTALL
+
+  CANCEL_INSTALL:
+    Quit
+
+  CLOSE:
+    DetailPrint "Waiting for Second Life to shut down..."
+    SendMessage $0 16 0 0
+
+  LOOP:
+	  FindWindow $0 "Second Life" ""
+	  IntCmp $0 0 DONE
+	  Sleep 500
+	  Goto LOOP
+
+  DONE:
+    Pop $0
+    Return
+FunctionEnd
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Delete files in Documents and Settings\<user>\SecondLife\cache
+; Delete files in Documents and Settings\All Users\SecondLife\cache
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function RemoveCacheFiles
+
+; Delete files in Documents and Settings\<user>\SecondLife
+Push $0
+Push $1
+Push $2
+  DetailPrint "Deleting cache files in Documents and Settings folder"
+
+  StrCpy $0 0 ; Index number used to iterate via EnumRegKey
+
+  LOOP:
+    EnumRegKey $1 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $0
+    StrCmp $1 "" DONE               ; no more users
+
+    ReadRegStr $2 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$1" "ProfileImagePath" 
+    StrCmp $2 "" CONTINUE 0         ; "ProfileImagePath" value is missing
+
+    ; Required since ProfileImagePath is of type REG_EXPAND_SZ
+    ExpandEnvStrings $2 $2
+
+	; When explicitly uninstalling, everything goes away
+    RMDir /r "$2\Application Data\SecondLife\cache"
+
+  CONTINUE:
+    IntOp $0 $0 + 1
+    Goto LOOP
+  DONE:
+Pop $2
+Pop $1
+Pop $0
+
+; Delete files in Documents and Settings\All Users\SecondLife
+Push $0
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" "Common AppData"
+  StrCmp $0 "" +2
+  RMDir /r "$0\SecondLife\cache"
+Pop $0
+
+; Delete filse in C:\Windows\Application Data\SecondLife
+; If the user is running on a pre-NT system, Application Data lives here instead of
+; in Documents and Settings.
+RMDir /r "$WINDIR\Application Data\SecondLife\cache"
+
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Delete files in Documents and Settings\<user>\SecondLife
+; Delete files in Documents and Settings\All Users\SecondLife
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function un.DocumentsAndSettingsFolder
+
+; Delete files in Documents and Settings\<user>\SecondLife
+Push $0
+Push $1
+Push $2
+
+  DetailPrint "Deleting files in Documents and Settings folder"
+
+  StrCpy $0 0 ; Index number used to iterate via EnumRegKey
+
+  LOOP:
+    EnumRegKey $1 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $0
+    StrCmp $1 "" DONE               ; no more users
+
+    ReadRegStr $2 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$1" "ProfileImagePath" 
+    StrCmp $2 "" CONTINUE 0         ; "ProfileImagePath" value is missing
+
+    ; Required since ProfileImagePath is of type REG_EXPAND_SZ
+    ExpandEnvStrings $2 $2
+
+	; If uninstalling a normal install remove everything
+	; Otherwise (preview/dmz etc) just remove cache
+    StrCmp $INSTFLAGS "" RM_ALL RM_CACHE
+      RM_ALL:
+        RMDir /r "$2\Application Data\SecondLife"
+        GoTo CONTINUE
+      RM_CACHE:
+        RMDir /r "$2\Application Data\SecondLife\Cache"
+
+  CONTINUE:
+    IntOp $0 $0 + 1
+    Goto LOOP
+  DONE:
+
+Pop $2
+Pop $1
+Pop $0
+
+; Delete files in Documents and Settings\All Users\SecondLife
+Push $0
+  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" "Common AppData"
+  StrCmp $0 "" +2
+  RMDir /r "$0\SecondLife"
+Pop $0
+
+; Delete filse in C:\Windows\Application Data\SecondLife
+; If the user is running on a pre-NT system, Application Data lives here instead of
+; in Documents and Settings.
+RMDir /r "$WINDIR\Application Data\SecondLife"
+
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; Close the program, if running. Modifies no variables.
+; Allows user to bail out of uninstall process.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function un.CloseSecondLife
+  Push $0
+  FindWindow $0 "Second Life" ""
+  IntCmp $0 0 DONE
+  MessageBox MB_OKCANCEL "Second Life can't be uninstalled while it is already running.$\n$\nFinish what you're doing then select OK to close Second Life and continue.$\nSelect CANCEL to cancel installation." IDOK CLOSE IDCANCEL CANCEL_UNINSTALL
+
+  CANCEL_UNINSTALL:
+    Quit
+
+  CLOSE:
+    DetailPrint "Waiting for Second Life to shut down..."
+    SendMessage $0 16 0 0
+
+  LOOP:
+	  FindWindow $0 "Second Life" ""
+	  IntCmp $0 0 DONE
+	  Sleep 500
+	  Goto LOOP
+
+  DONE:
+    Pop $0
+    Return
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Delete the installed files
+;;; This deletes the uninstall executable, but it works 
+;;; because it is copied to temp directory before running
+;;;
+;;; Note:  You must list all files here, because we only
+;;; want to delete our files, not things users left in the
+;;; application directories.
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Function un.ProgramFiles
+
+;; Remove mozilla file first so recursive directory deletion doesn't get hung up
+Delete "$INSTDIR\app_settings\mozilla\components"
+
+;; This placeholder is replaced by the complete list of files to uninstall by viewer_manifest.py
+%%DELETE_FILES%%
+
+;; Optional/obsolete files.  Delete won't fail if they don't exist.
+Delete "$INSTDIR\dronesettings.ini"
+Delete "$INSTDIR\message_template.msg"
+Delete "$INSTDIR\newview.pdb"
+Delete "$INSTDIR\newview.map"
+Delete "$INSTDIR\SecondLife.pdb"
+Delete "$INSTDIR\SecondLife.map"
+Delete "$INSTDIR\comm.dat"
+Delete "$INSTDIR\*.glsl"
+Delete "$INSTDIR\motions\*.lla"
+Delete "$INSTDIR\trial\*.html"
+Delete "$INSTDIR\newview.exe"
+;; Remove entire help directory
+Delete "$INSTDIR\help\Advanced\*"
+RMDir  "$INSTDIR\help\Advanced"
+Delete "$INSTDIR\help\basics\*"
+RMDir  "$INSTDIR\help\basics"
+Delete "$INSTDIR\help\Concepts\*"
+RMDir  "$INSTDIR\help\Concepts"
+Delete "$INSTDIR\help\welcome\*"
+RMDir  "$INSTDIR\help\welcome"
+Delete "$INSTDIR\help\*"
+RMDir  "$INSTDIR\help"
+
+Delete "$INSTDIR\uninst.exe"
+RMDir "$INSTDIR"
+
+IfFileExists "$INSTDIR" FOLDERFOUND NOFOLDER
+
+FOLDERFOUND:
+  MessageBox MB_YESNO "There are still files in your SecondLife program directory.$\n$\nThese are possibly files you created or moved to:$\n$INSTDIR$\n$\nDo you want to remove them?" IDNO NOFOLDER
+  RMDir /r "$INSTDIR"
+
+NOFOLDER:
+
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Uninstall settings
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+UninstallText "This will uninstall Second Life ${VERSION_LONG} from your system."
+ShowUninstDetails show
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Uninstall section
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+Section Uninstall
+
+; Start with some default values.
+StrCpy $INSTFLAGS "${FARMFLAG}"
+StrCpy $INSTPROG "${INSTNAME}"
+StrCpy $INSTEXE "${INSTEXE}"
+StrCpy $INSTSHORTCUT "${SHORTCUT}"
+Call un.CheckStartupParams              ; Figure out where, what and how to uninstall.
+Call un.CheckIfAdministrator		; Make sure the user can install/uninstall
+
+; uninstall for all users (if you change this, change it in the install as well)
+SetShellVarContext all			
+
+; Make sure we're not running
+Call un.CloseSecondLife
+
+; Clean up registry keys
+DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG"
+DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG"
+
+; Clean up shortcuts
+Delete "$SMPROGRAMS\$INSTSHORTCUT\*.*"
+RMDir  "$SMPROGRAMS\$INSTSHORTCUT"
+
+Delete "$DESKTOP\$INSTSHORTCUT.lnk"
+Delete "$INSTDIR\$INSTSHORTCUT.lnk"
+Delete "$INSTDIR\Uninstall $INSTSHORTCUT.lnk"
+
+; Clean up cache and log files.
+; Leave them in-place for non AGNI installs.
+
+!ifdef UNINSTALL_SETTINGS
+Call un.DocumentsAndSettingsFolder
+!endif
+
+Call un.ProgramFiles
+
+SectionEnd 				; end of uninstall section
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; (From the NSIS wiki, DK)
+; GetParameterValue
+;
+; Usage:
+; !insertmacro GetParameterValue "/L=" "1033"
+; pop $R0
+;
+; Returns on top of stack
+;
+; Example command lines:
+; foo.exe /S /L=1033 /D=C:\Program Files\Foo
+; or:
+; foo.exe /S "/L=1033" /D="C:\Program Files\Foo"
+; gpv "/L=" "1033"
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+ !macro GetParameterValue SWITCH DEFAULT
+   Push $0
+   Push $1
+   Push $2
+   Push $3
+   Push $4
+
+ ;$CMDLINE='"My Setup\Setup.exe" /L=1033 /S'
+   Push "$CMDLINE"
+   Push '${SWITCH}"'
+   !insertmacro StrStr
+   Pop $0
+   StrCmp "$0" "" gpv_notquoted
+ ;$0='/L="1033" /S'
+   StrLen $2 "$0"
+   Strlen $1 "${SWITCH}"
+   IntOp $1 $1 + 1
+   StrCpy $0 "$0" $2 $1
+ ;$0='1033" /S'
+   Push "$0"
+   Push '"'
+   !insertmacro StrStr
+   Pop $1
+   StrLen $2 "$0"
+   StrLen $3 "$1"
+   IntOp $4 $2 - $3
+   StrCpy $0 $0 $4 0
+   Goto gpv_done
+
+   gpv_notquoted:
+   Push "$CMDLINE"
+   Push "${SWITCH}"
+   !insertmacro StrStr
+   Pop $0
+   StrCmp "$0" "" gpv_done
+ ;$0='/L="1033" /S'
+   StrLen $2 "$0"
+   Strlen $1 "${SWITCH}"
+   StrCpy $0 "$0" $2 $1
+ ;$0=1033 /S'
+   Push "$0"
+   Push ' '
+   !insertmacro StrStr
+   Pop $1
+   StrLen $2 "$0"
+   StrLen $3 "$1"
+   IntOp $4 $2 - $3
+   StrCpy $0 $0 $4 0
+   Goto gpv_done
+
+   gpv_done:
+   StrCmp "$0" "" 0 +2
+   StrCpy $0 "${DEFAULT}"
+
+   Pop $4
+   Pop $3
+   Pop $2
+   Pop $1
+   Exch $0
+ !macroend
+
+; And I had to modify StrStr a tiny bit.
+; Possible upgrade switch the goto's to use ${__LINE__}
+
+!macro STRSTR
+  Exch $R1 ; st=haystack,old$R1, $R1=needle
+  Exch    ; st=old$R1,haystack
+  Exch $R2 ; st=old$R1,old$R2, $R2=haystack
+  Push $R3
+  Push $R4
+  Push $R5
+  StrLen $R3 $R1
+  StrCpy $R4 0
+  ; $R1=needle
+  ; $R2=haystack
+  ; $R3=len(needle)
+  ; $R4=cnt
+  ; $R5=tmp
+ ;  loop;
+    StrCpy $R5 $R2 $R3 $R4
+    StrCmp $R5 $R1 +4
+    StrCmp $R5 "" +3
+    IntOp $R4 $R4 + 1
+    Goto -4
+ ;  done;
+  StrCpy $R1 $R2 "" $R4
+  Pop $R5
+  Pop $R4
+  Pop $R3
+  Pop $R2
+  Exch $R1
+!macroend
+
+Function GetProgramName
+  !insertmacro GetParameterValue "/P=" "SecondLife"
+FunctionEnd
+
+Function un.GetProgramName
+  !insertmacro GetParameterValue "/P=" "SecondLife"
+FunctionEnd
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+; (From the NSIS documentation, JC)
+; GetWindowsVersion
+;
+; Based on Yazno's function, http://yazno.tripod.com/powerpimpit/
+; Updated by Joost Verburg
+;
+; Returns on top of stack
+;
+; Windows Version (95, 98, ME, NT x.x, 2000, XP, 2003)
+; or
+; '' (Unknown Windows Version)
+;
+; Usage:
+;   Call GetWindowsVersion
+;   Pop $R0
+;   ; at this point $R0 is "NT 4.0" or whatnot
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+ 
+Function GetWindowsVersion
+ 
+   Push $R0
+   Push $R1
+ 
+   ReadRegStr $R0 HKLM \
+   "SOFTWARE\Microsoft\Windows NT\CurrentVersion" CurrentVersion
+
+   IfErrors 0 lbl_winnt
+   
+   ; we are not NT
+   ReadRegStr $R0 HKLM \
+   "SOFTWARE\Microsoft\Windows\CurrentVersion" VersionNumber
+ 
+   StrCpy $R1 $R0 1
+   StrCmp $R1 '4' 0 lbl_error
+ 
+   StrCpy $R1 $R0 3
+ 
+   StrCmp $R1 '4.0' lbl_win32_95
+   StrCmp $R1 '4.9' lbl_win32_ME lbl_win32_98
+ 
+   lbl_win32_95:
+     StrCpy $R0 '95'
+   Goto lbl_done
+ 
+   lbl_win32_98:
+     StrCpy $R0 '98'
+   Goto lbl_done
+ 
+   lbl_win32_ME:
+     StrCpy $R0 'ME'
+   Goto lbl_done
+ 
+   lbl_winnt:
+ 
+   StrCpy $R1 $R0 1
+ 
+   StrCmp $R1 '3' lbl_winnt_x
+   StrCmp $R1 '4' lbl_winnt_x
+ 
+   StrCpy $R1 $R0 3
+ 
+   StrCmp $R1 '5.0' lbl_winnt_2000
+   StrCmp $R1 '5.1' lbl_winnt_XP
+   StrCmp $R1 '5.2' lbl_winnt_2003 lbl_error
+ 
+   lbl_winnt_x:
+     StrCpy $R0 "NT $R0" 6
+   Goto lbl_done
+ 
+   lbl_winnt_2000:
+     Strcpy $R0 '2000'
+   Goto lbl_done
+ 
+   lbl_winnt_XP:
+     Strcpy $R0 'XP'
+   Goto lbl_done
+ 
+   lbl_winnt_2003:
+     Strcpy $R0 '2003'
+   Goto lbl_done
+ 
+   lbl_error:
+     Strcpy $R0 ''
+   lbl_done:
+ 
+   Pop $R1
+   Exch $R0
+ 
+FunctionEnd
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
new file mode 100755
index 00000000000..104d860c870
--- /dev/null
+++ b/indra/newview/viewer_manifest.py
@@ -0,0 +1,437 @@
+#!/usr/bin/python
+# @file viewer_manifest.py
+# @author Ryan Williams
+# @brief Description of all installer viewer files, and methods for packaging
+#        them into installers for all supported platforms.
+#
+# Copyright (c) 2006-$CurrentYear$, Linden Research, Inc.
+# $License$
+import sys
+import os.path
+import re
+import tarfile
+viewer_dir = os.path.dirname(__file__)
+# add llmanifest library to our path so you don't have to muck with PYTHONPATH
+sys.path.append(os.path.join(viewer_dir, '../lib/python/indra'))
+from llmanifest import LLManifest, main, proper_windows_path, path_ancestors
+
+class ViewerManifest(LLManifest):
+        def construct(self):
+                super(ViewerManifest, self).construct()
+                self.exclude("*.svn*")
+                self.path(src="../../scripts/messages/message_template.msg", dst="app_settings/message_template.msg")
+
+                if self.prefix(src="app_settings"):
+                        self.exclude("logcontrol.xml")
+                        self.exclude("logcontrol-dev.xml")
+                        self.path("*.pem")
+                        self.path("*.ini")
+                        self.path("*.xml")
+                        self.path("*.vp")
+                        self.path("*.db2")
+
+                        # include the entire shaders directory recursively
+                        self.path("shaders")
+                        self.end_prefix("app_settings")
+
+                if self.prefix(src="character"):
+                        self.path("*.llm")
+                        self.path("*.xml")
+                        self.path("*.tga")
+                        self.end_prefix("character")
+
+
+                # Include our fonts
+                if self.prefix(src="fonts"):
+                        self.path("*.ttf")
+                        self.path("*.txt")
+                        self.end_prefix("fonts")
+
+                # XUI
+                if self.prefix(src="skins"):
+                        self.path("paths.xml")
+                        self.path("xui/*/*.xml")
+                        self.path('words.*.txt')
+
+                        # Local HTML files (e.g. loading screen)
+                        if self.prefix("html/*"):
+                                self.path("*.html")
+                                self.path("*.gif")
+                                self.path("*.jpg")
+                                self.path("*.css")
+                                self.end_prefix("html/*")
+                        self.end_prefix("skins")
+
+                self.path("featuretable.txt")
+                self.path("releasenotes.txt")
+                self.path("lsl_guide.html")
+                self.path("gpu_table.txt")
+
+        def flags_list(self):
+                """ Convenience function that returns the command-line flags for the grid"""
+                if(self.args['grid'] == ''):
+                        return ""
+                elif(self.args['grid'] == 'firstlook'):
+                        return '-settings settings_firstlook.xml'
+                else:
+			return ("-settings settings_beta.xml --%(grid)s -helperuri http://preview-%(grid)s.secondlife.com/helpers/" % {'grid':self.args['grid']})
+
+        def login_url(self):
+                """ Convenience function that returns the appropriate login url for the grid"""
+                if(self.args.get('login_url')):
+                        return self.args['login_url']
+                else:
+                        if(self.args['grid'] == ''):
+                                return 'http://secondlife.com/app/login/'
+                        elif(self.args['grid'] == 'firstlook'):
+                                return 'http://secondlife.com/app/login/firstlook/'
+                        else:
+                                return 'http://secondlife.com/app/login/beta/'
+
+        def replace_login_url(self):
+                # set the login page to point to a url appropriate for the type of client
+                self.replace_in("skins/xui/en-us/panel_login.xml", searchdict={'http://secondlife.com/app/login/':self.login_url()})
+
+        def create_unpacked(self):
+                unpacked_file_name = "unpacked_%(plat)s_%(vers)s.tar" % {
+                        'plat':self.args['platform'],
+                        'vers':'_'.join(self.args['version'])}
+                print "Creating unpacked file:", unpacked_file_name
+                # could add a gz here but that doubles the time it takes to do this step
+                tf = tarfile.open(self.src_path_of(unpacked_file_name), 'w:')
+                # add the entire installation package, at the very top level
+                tf.add(self.get_dst_prefix(), "")
+                tf.close()
+
+class WindowsManifest(ViewerManifest):
+        def final_exe(self):
+                # *NOTE: these are the only two executable names that the crash reporter recognizes
+                if self.args['grid'] == '':
+                        return "SecondLife.exe"
+                else:
+                        return "SecondLifePreview.exe"
+                        # return "SecondLifePreview%s.exe" % (self.args['grid'], )
+
+        def construct(self):
+                super(WindowsManifest, self).construct()
+                # the final exe is complicated because we're not sure where it's coming from,
+                # nor do we have a fixed name for the executable
+                self.path(self.find_existing_file('ReleaseForDownload/Secondlife.exe', 'Secondlife.exe', 'ReleaseNoOpt/newview_noopt.exe'), dst=self.final_exe())
+                # need to get the kdu dll from any of the build directories as well
+                self.path(self.find_existing_file('ReleaseForDownload/llkdu.dll', 'llkdu.dll', '../../libraries/i686-win32/lib_release/llkdu.dll'), dst='llkdu.dll')
+                self.path(src="licenses-win32.txt", dst="licenses.txt")
+
+                # For use in crash reporting (generates minidumps)
+                self.path("dbghelp.dll")
+
+                # For using FMOD for sound... DJS
+                self.path("fmod.dll")
+
+                # Mozilla appears to force a dependency on these files so we need to ship it (CP)
+                self.path("msvcr71.dll")
+                self.path("msvcp71.dll")
+
+                # Mozilla runtime DLLs (CP)
+                if self.prefix(src="../../libraries/i686-win32/lib_release", dst=""):
+                        self.path("gksvggdiplus.dll")
+                        self.path("js3250.dll")
+                        self.path("nspr4.dll")
+                        self.path("nss3.dll")
+                        self.path("nssckbi.dll")
+                        self.path("plc4.dll")
+                        self.path("plds4.dll")
+                        self.path("smime3.dll")
+                        self.path("softokn3.dll")
+                        self.path("ssl3.dll")
+                        self.path("xpcom.dll")
+                        self.path("xul.dll")
+                        self.end_prefix()
+
+                # Mozilla runtime misc files (CP)
+                if self.prefix(src="app_settings/mozilla"):
+                        self.path("chrome/*.*")
+                        self.path("components/*.*")
+                        self.path("greprefs/*.*")
+                        self.path("plugins/*.*")
+                        self.path("res/*.*")
+                        self.path("res/*/*")
+                        self.end_prefix()
+
+                # pull in the crash logger and updater from other projects
+                self.path(src="../win_crash_logger/win_crash_logger.exe", dst="win_crash_logger.exe")
+                self.path(src="../win_updater/updater.exe", dst="updater.exe")
+                self.replace_login_url()
+
+        def nsi_file_commands(self, install=True):
+                def wpath(path):
+                        if(path.endswith('/') or path.endswith(os.path.sep)):
+                                path = path[:-1]
+                        path = path.replace('/', '\\')
+                        return path
+
+                result = ""
+                dest_files = [pair[1] for pair in self.file_list if pair[0] and os.path.isfile(pair[1])]
+                # sort deepest hierarchy first
+                dest_files.sort(lambda a,b: cmp(a.count(os.path.sep),b.count(os.path.sep)) or cmp(a,b))
+                dest_files.reverse()
+                out_path = None
+                for pkg_file in dest_files:
+                        rel_file = os.path.normpath(pkg_file.replace(self.get_dst_prefix()+os.path.sep,''))
+                        installed_dir = wpath(os.path.join('$INSTDIR', os.path.dirname(rel_file)))
+                        pkg_file = wpath(os.path.normpath(pkg_file))
+                        if installed_dir != out_path:
+                                if(install):
+                                        out_path = installed_dir
+                                        result += 'SetOutPath ' + out_path + '\n'
+                        if(install):
+                                result += 'File ' + pkg_file + '\n'
+                        else:
+                                result += 'Delete ' + wpath(os.path.join('$INSTDIR', rel_file)) + '\n'
+		# at the end of a delete, just rmdir all the directories
+		if(not install):
+			deleted_file_dirs = [os.path.dirname(pair[1].replace(self.get_dst_prefix()+os.path.sep,'')) for pair in self.file_list]
+			# find all ancestors so that we don't skip any dirs that happened to have no non-dir children
+			deleted_dirs = []
+			for d in deleted_file_dirs:
+				deleted_dirs.extend(path_ancestors(d))
+			# sort deepest hierarchy first
+			deleted_dirs.sort(lambda a,b: cmp(a.count(os.path.sep),b.count(os.path.sep)) or cmp(a,b))
+			deleted_dirs.reverse()
+			prev = None
+			for d in deleted_dirs:
+				if d != prev:   # skip duplicates
+					result += 'RMDir ' + wpath(os.path.join('$INSTDIR', os.path.normpath(d))) + '\n'
+				prev = d
+
+                return result
+
+        def package_finish(self):
+                self.create_unpacked()
+                version_vars_template = """
+                !define INSTEXE  "%(final_exe)s"
+                !define VERSION "%(version_short)s"
+                !define VERSION_LONG "%(version)s"
+                !define VERSION_DASHES "%(version_dashes)s"
+                """
+                if(self.args['grid'] == ''):
+                        installer_file = "Second Life %(version_dashes)s Setup.exe"
+                        grid_vars_template = """
+                        OutFile "%(outfile)s"
+                        !define INSTFLAGS "%(flags)s"
+                        !define INSTNAME   "SecondLife"
+                        !define SHORTCUT   "Second Life"
+                        !define URLNAME   "secondlife"
+                        Caption "Second Life ${VERSION}"
+                        """
+                else:
+                        installer_file = "Second Life %(version_dashes)s (%(grid_caps)s) Setup.exe"
+                        grid_vars_template = """
+                        OutFile "%(outfile)s"
+			!define INSTFLAGS "%(flags)s"
+                        !define INSTNAME   "SecondLife%(grid_caps)s"
+                        !define SHORTCUT   "Second Life (%(grid_caps)s)"
+                        !define URLNAME   "secondlife%(grid)s"
+                        !define UNINSTALL_SETTINGS 1
+                        Caption "Second Life %(grid)s ${VERSION}"
+                        """
+                if(self.args.has_key('installer_name')):
+                        installer_file = self.args['installer_name']
+                else:
+                        installer_file = installer_file % {'version_dashes' : '-'.join(self.args['version']),
+                                                                                           'grid_caps' : self.args['grid'].upper()}
+                tempfile = "../secondlife_setup.nsi"
+                # the following is an odd sort of double-string replacement
+                self.replace_in("installers/windows/installer_template.nsi", tempfile, {
+                        "%%VERSION%%":version_vars_template%{'version_short' : '.'.join(self.args['version'][:-1]),
+                                                                                                 'version' : '.'.join(self.args['version']),
+                                                                                                 'version_dashes' : '-'.join(self.args['version']),
+                                                                                                 'final_exe' : self.final_exe()},
+                        "%%GRID_VARS%%":grid_vars_template%{'grid':self.args['grid'],
+                                                                                                'grid_caps':self.args['grid'].upper(),
+                                                                                                'outfile':installer_file,
+                                                                                                'flags':self.flags_list()},
+                        "%%INSTALL_FILES%%":self.nsi_file_commands(True),
+                        "%%DELETE_FILES%%":self.nsi_file_commands(False)})
+
+                NSIS_path = 'C:\\Program Files\\NSIS\\makensis.exe'
+                self.run_command('"' + proper_windows_path(NSIS_path) + '" ' + self.dst_path_of(tempfile))
+#                self.remove(self.dst_path_of(tempfile))
+                self.created_path(installer_file)
+
+
+class DarwinManifest(ViewerManifest):
+        def construct(self):
+                # copy over the build result (this is a no-op if run within the xcode script)
+                self.path("build/" + self.args['configuration'] + "/Second Life.app", dst="")
+
+                if self.prefix(src="", dst="Contents"):  # everything goes in Contents
+                        # Expand the tar file containing the assorted mozilla bits into
+                        #  <bundle>/Contents/MacOS/
+                        self.contents_of_tar('mozilla-universal-darwin.tgz', 'MacOS')
+#                       self.run_command('tar -zx -C "%(macos)s" -f "%(tarfile)s"' %
+#                                                        {'macos':self.ensure_dst_dir("MacOS"),
+#                                                         'tarfile':self.src_path_of("mozilla-universal-darwin.tgz")})
+
+                        # replace the default theme with our custom theme (so scrollbars work).
+                        if self.prefix(src="mozilla-theme", dst="MacOS/chrome"):
+                                self.path("classic.jar")
+                                self.path("classic.manifest")
+                                self.end_prefix("MacOS/chrome")
+
+                        # most everything goes in the Resources directory
+                        if self.prefix(src="", dst="Resources"):
+                                super(DarwinManifest, self).construct()
+
+                                # the trial directory seems to be not used [rdw]
+                                self.path('trial')
+
+                                if self.prefix("cursors_mac"):
+                                        self.path("*.tif")
+                                        self.end_prefix("cursors_mac")
+
+                                self.path("licenses-mac.txt", dst="licenses.txt")
+                                self.path("featuretable_mac.txt")
+                                self.path("secondlife.icns")
+
+                                # llkdu dynamic library
+                                self.path("../../libraries/universal-darwin/lib_release/libllkdu.dylib", "libllkdu.dylib")
+
+                                # command line arguments for connecting to the proper grid
+                                self.put_in_file(self.flags_list(), 'arguments.txt')
+
+                                # set the proper login url
+                                self.replace_login_url()
+
+                                self.end_prefix("Resources")
+
+                        self.end_prefix("Contents")
+
+
+        def package_finish(self):
+                # NOTE: the -S argument to strip causes it to keep enough info for
+                # annotated backtraces (i.e. function names in the crash log).  'strip' with no
+                # arguments yields a slightly smaller binary but makes crash logs mostly useless.
+                # This may be desirable for the final release.  Or not.
+                self.run_command('strip -S "%(viewer_binary)s"' %
+                                                 { 'viewer_binary' : self.dst_path_of('Contents/MacOS/Second Life')})
+
+                self.create_unpacked()
+
+                imagename="SecondLife_" + '_'.join(self.args['version'])
+                if(self.args['grid'] != ''):
+                        imagename = imagename + '_' + self.args['grid']
+
+                sparsename = imagename + ".sparseimage"
+                finalname = imagename + ".dmg"
+                # make sure we don't have stale files laying about
+                self.remove(sparsename, finalname)
+
+                self.run_command('hdiutil create "%(sparse)s" -volname "Second Life" -fs HFS+ -type SPARSE -megabytes 200' % {'sparse':sparsename})
+
+                # mount the image and get the name of the mount point and device node
+                hdi_output = self.run_command('hdiutil attach -private "' + sparsename + '"')
+                devfile = re.search("/dev/disk([0-9]+)[^s]", hdi_output).group(0).strip()
+                volpath = re.search('HFS\s+(.+)', hdi_output).group(1).strip()
+
+                # Copy everything in to the mounted .dmg
+                for s,d in {self.get_dst_prefix():("Second Life " + self.args['grid']).strip()+ ".app",
+                                        "lsl_guide.html":"Linden Scripting Language Guide.html",
+                                        "releasenotes.txt":"Release Notes.txt",
+                                        "installers/darwin/mac_image_hidden":".hidden",
+                                        "installers/darwin/mac_image_background.tga":"background.tga",
+                                        "installers/darwin/mac_image_DS_Store":".DS_Store"}.items():
+                        print "Copying to dmg", s, d
+                        self.copy_action(self.src_path_of(s), os.path.join(volpath, d))
+
+                # Unmount the image
+                self.run_command('hdiutil detach "' + devfile + '"')
+
+                print "Converting temp disk image to final disk image"
+                self.run_command('hdiutil convert "%(sparse)s" -format UDZO -imagekey zlib-level=9 -o "%(final)s"' % {'sparse':sparsename, 'final':finalname})
+                # get rid of the temp file
+                self.remove(sparsename)
+
+class LinuxManifest(ViewerManifest):
+        def construct(self):
+                super(LinuxManifest, self).construct()
+                self.path("licenses-linux.txt","licenses.txt")
+                self.path("res/ll_icon.ico","secondlife.ico")
+                if self.prefix("linux_tools", ""):
+                        self.path("client-readme.txt","README-linux.txt")
+                        self.path("wrapper.sh","secondlife")
+                        self.path("unicode.ttf","unicode.ttf")
+                        self.end_prefix("linux_tools")
+
+                # Create an appropriate gridargs.dat for this package, denoting required grid.
+                self.put_in_file(self.flags_list(), 'gridargs.dat')
+                # set proper login url
+                self.replace_login_url()
+
+        def package_finish(self):
+                if(self.args.has_key('installer_name')):
+                        installer_name = self.args['installer_name']
+                else:
+                        installer_name = '_'.join('SecondLife_', self.args.get('arch'), *self.args['version'])
+                        if grid != '':
+                                installer_name += "_" + grid.upper()
+
+                # stripping all the libs removes a few megabytes from the end-user package
+                for s,d in self.file_list:
+                        if re.search("lib/lib.+\.so.*", d):
+                                self.run_command('strip -S %s' % d)
+                self.create_unpacked()
+                # temporarily move directory tree so that it has the right name in the tarfile
+                self.run_command("mv %(dst)s %(inst)s" % {'dst':self.get_dst_prefix(),'inst':self.src_path_of(installer_name)})
+                # --numeric-owner hides the username of the builder for security etc.
+                self.run_command('tar -C %(dir)s --numeric-owner -cjf %(inst_path)s.tar.bz2 %(inst_name)s' % {'dir':self.get_src_prefix(), 'inst_name': installer_name, 'inst_path':self.src_path_of(installer_name)})
+                self.run_command("mv %(inst)s %(dst)s" % {'dst':self.get_dst_prefix(),'inst':self.src_path_of(installer_name)})
+
+class Linux_i686Manifest(LinuxManifest):
+        def construct(self):
+                super(Linux_i686Manifest, self).construct()
+                self.path("secondlife-i686-bin-stripped","bin/do-not-directly-run-secondlife-bin")
+                self.path("../linux_crash_logger/linux-crash-logger-i686-bin-stripped","linux-crash-logger.bin")
+                self.path("linux_tools/launch_url.sh","launch_url.sh")
+                if self.prefix("res-sdl"):
+                        self.path("*")
+                        # recurse
+                        self.end_prefix("res-sdl")
+
+                self.path("app_settings/mozilla-runtime-linux-i686", "app_settings/mozilla")
+
+                if self.prefix("../../libraries/i686-linux/lib_release_client", "lib"):
+                        self.path("libkdu_v42R.so")
+                        self.path("libfmod-3.75.so")
+                        self.path("libapr-1.so.0")
+                        self.path("libaprutil-1.so.0")
+                        self.path("libdb-4.2.so")
+                        self.path("libogg.so.0")
+                        self.path("libvorbis.so.0")
+                        self.path("libvorbisfile.so.0")
+                        self.path("libvorbisenc.so.0")
+                        self.path("libcurl.so.3")
+                        self.path("libcrypto.so.0.9.7")
+                        self.path("libssl.so.0.9.7")
+                        self.path("libexpat.so.1")
+                        self.path("libstdc++.so.6")
+                        self.path("libelfio.so")
+                        self.path("libuuid.so", "libuuid.so.1")
+                        self.path("libSDL-1.2.so.0")
+                        self.path("libllkdu.so", "../bin/libllkdu.so") # llkdu goes in bin for some reason
+                        self.end_prefix("lib")
+
+
+class Linux_x86_64Manifest(LinuxManifest):
+        def construct(self):
+                super(Linux_x86_64Manifest, self).construct()
+                self.path("secondlife-x86_64-bin-stripped","bin/secondlife-bin")
+                # TODO: I get the sense that this isn't fully fleshed out
+                if self.prefix("../../libraries/x86_64-linux/lib_release_client", "lib"):
+                        self.path("libkdu_v42R.so")
+                        self.path("libxmlrpc.so.0")
+                        # self.path("libllkdu.so", "../bin/libllkdu.so") # llkdu goes in bin for some reason
+                        self.end_prefix("lib")
+
+
+if __name__ == "__main__":
+        main(srctree=viewer_dir, dsttree=os.path.join(viewer_dir, "packaged"))
diff --git a/indra/test/test_llmanifest.py b/indra/test/test_llmanifest.py
new file mode 100644
index 00000000000..cc464237a29
--- /dev/null
+++ b/indra/test/test_llmanifest.py
@@ -0,0 +1,109 @@
+#!/usr/bin/python
+# @file test_llmanifest.py
+# @author Ryan Williams
+# @brief Test cases for LLManifest library.
+#
+# Copyright (c) 2006-$CurrentYear$, Linden Research, Inc.
+# $License$
+
+from indra import llmanifest
+import os.path
+import os
+import unittest
+
+class DemoManifest(llmanifest.LLManifest):
+    def construct(self):
+        super(DemoManifest, self).construct()
+        if self.prefix("dir_1"):
+            self.path("test_a")
+            self.path(src="test_b", dst="test_dst_b")
+            self.path("*.test")
+            self.path("*.tex", "*.jpg")
+            if self.prefix("nested", dst=""):
+                self.path("deep")
+                self.end_prefix()
+            self.end_prefix("dir_1")
+
+
+class Demo_ArchManifest(llmanifest.LLManifest):
+        pass
+
+class TestLLManifest(unittest.TestCase):
+    mode='static'
+    def setUp(self):
+        self.m = llmanifest.LLManifest("src", "dst", {'grid':'default', 'platform':'darwin', 'version':(1,2,3,4)})
+
+    def testproperwindowspath(self):
+        self.assertEqual(llmanifest.proper_windows_path("C:\Program Files", "cygwin"),"/cygdrive/c/Program Files")
+        self.assertEqual(llmanifest.proper_windows_path("C:\Program Files", "windows"), "C:\Program Files")
+        self.assertEqual(llmanifest.proper_windows_path("/cygdrive/c/Program Files/NSIS", "windows"), "C:\Program Files\NSIS")
+        self.assertEqual(llmanifest.proper_windows_path("/cygdrive/c/Program Files/NSIS", "cygwin"), "/cygdrive/c/Program Files/NSIS")
+
+    def testpathancestors(self):
+        self.assertEqual(["dir"], [p for p in llmanifest.path_ancestors("dir")])
+        self.assertEqual(["dir/sub", "dir"], [p for p in llmanifest.path_ancestors("dir/sub")])
+        self.assertEqual(["dir/sub", "dir"], [p for p in llmanifest.path_ancestors("dir/sub/")])
+        self.assertEqual(["dir/sub/two", "dir/sub", "dir"], [p for p in llmanifest.path_ancestors("dir/sub/two")])
+
+
+    def testforplatform(self):
+        self.assertEqual(llmanifest.LLManifest.for_platform('demo'), DemoManifest)
+        def tmp_test():
+            return llmanifest.LLManifest.for_platform('extant')
+        self.assertRaises(KeyError, tmp_test)
+        ExtantManifest = llmanifest.LLManifestRegistry('ExtantManifest', (llmanifest.LLManifest,), {})
+        self.assertEqual(llmanifest.LLManifest.for_platform('extant'), ExtantManifest)
+        self.assertEqual(llmanifest.LLManifest.for_platform('demo', 'Arch'), Demo_ArchManifest)
+
+
+    def testprefix(self):
+        self.assertEqual(self.m.get_src_prefix(), "src")
+        self.assertEqual(self.m.get_dst_prefix(), "dst")
+        self.m.prefix("level1")
+        self.assertEqual(self.m.get_src_prefix(), "src/level1")
+        self.assertEqual(self.m.get_dst_prefix(), "dst/level1")
+        self.m.end_prefix()
+        self.m.prefix(src="src", dst="dst")
+        self.assertEqual(self.m.get_src_prefix(), "src/src")
+        self.assertEqual(self.m.get_dst_prefix(), "dst/dst")
+        self.m.end_prefix()
+
+    def testendprefix(self):
+        self.assertEqual(self.m.get_src_prefix(), "src")
+        self.assertEqual(self.m.get_dst_prefix(), "dst")
+        self.m.prefix("levela")
+        self.m.end_prefix()
+        self.assertEqual(self.m.get_src_prefix(), "src")
+        self.assertEqual(self.m.get_dst_prefix(), "dst")
+        self.m.prefix("level1")
+        self.m.end_prefix("level1")
+        self.assertEqual(self.m.get_src_prefix(), "src")
+        self.assertEqual(self.m.get_dst_prefix(), "dst")
+        self.m.prefix("level1")
+        def tmp_test():
+            self.m.end_prefix("mismatch")
+        self.assertRaises(ValueError, tmp_test)
+
+    def testruncommand(self):
+        self.assertEqual("Hello\n", self.m.run_command("echo Hello"))
+        def tmp_test():
+            self.m.run_command("fff_garbage")
+        self.assertRaises(RuntimeError, tmp_test)
+
+    def testpathof(self):
+        self.assertEqual(self.m.src_path_of("a"), "src/a")
+        self.assertEqual(self.m.dst_path_of("a"), "dst/a")
+        self.m.prefix("tmp")
+        self.assertEqual(self.m.src_path_of("b/c"), "src/tmp/b/c")
+        self.assertEqual(self.m.dst_path_of("b/c"), "dst/tmp/b/c")
+
+    def testcmakedirs(self):
+        self.m.cmakedirs("test_dir_DELETE/nested/dir")
+        self.assert_(os.path.exists("test_dir_DELETE/nested/dir"))
+        self.assert_(os.path.isdir("test_dir_DELETE"))
+        self.assert_(os.path.isdir("test_dir_DELETE/nested"))
+        self.assert_(os.path.isdir("test_dir_DELETE/nested/dir"))
+        os.removedirs("test_dir_DELETE/nested/dir")
+
+if __name__ == '__main__':
+    unittest.main()
-- 
GitLab