phoenix-firestorm/indra/newview/viewer_manifest.py

1120 lines
51 KiB
Python

#!/usr/bin/env 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.
$LicenseInfo:firstyear=2006&license=viewerlgpl$
Second Life Viewer Source Code
Copyright (C) 2006-2011, Linden Research, Inc.
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation;
version 2.1 of the License only.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
$/LicenseInfo$
"""
import sys
import os.path
import re
import tarfile
import time
import random
viewer_dir = os.path.dirname(__file__)
# add llmanifest library to our path so we don't have to muck with PYTHONPATH
sys.path.append(os.path.join(viewer_dir, '../lib/python/indra/util'))
from llmanifest import LLManifest, main, proper_windows_path, path_ancestors
class ViewerManifest(LLManifest):
def is_packaging_viewer(self):
# Some commands, files will only be included
# if we are packaging the viewer on windows.
# This manifest is also used to copy
# files during the build (see copy_w_viewer_manifest
# and copy_l_viewer_manifest targets)
return 'package' in self.args['actions']
def construct(self):
super(ViewerManifest, self).construct()
self.exclude("*.svn*")
self.path(src="../../scripts/messages/message_template.msg", dst="app_settings/message_template.msg")
self.path(src="../../etc/message.xml", dst="app_settings/message.xml")
if self.is_packaging_viewer():
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("*.db2")
# include the entire shaders directory recursively
self.path("shaders")
# include the extracted list of contributors
contributor_names = self.extract_names("../../doc/contributions.txt")
self.put_in_file(contributor_names, "contributors.txt")
self.file_list.append(["../../doc/contributions.txt",self.dst_path_of("contributors.txt")])
# include the extracted list of translators
translator_names = self.extract_names("../../doc/translations.txt")
self.put_in_file(translator_names, "translators.txt")
self.file_list.append(["../../doc/translations.txt",self.dst_path_of("translators.txt")])
# 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 :
try:
linden_file = open(linden_names_path,'r')
# 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")
# ... and the included spell checking dictionaries
pkgdir = os.path.join(self.args['build'], os.pardir, 'packages')
if self.prefix(src=pkgdir,dst=""):
self.path("dictionaries")
self.end_prefix(pkgdir)
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")
# skins
if self.prefix(src="skins"):
self.path("paths.xml")
# include the entire textures directory recursively
if self.prefix(src="*/textures"):
self.path("*/*.tga")
self.path("*/*.j2c")
self.path("*/*.jpg")
self.path("*/*.png")
self.path("*.tga")
self.path("*.j2c")
self.path("*.jpg")
self.path("*.png")
self.path("textures.xml")
self.end_prefix("*/textures")
self.path("*/xui/*/*.xml")
self.path("*/xui/*/widgets/*.xml")
self.path("*/*.xml")
# Local HTML files (e.g. loading screen)
if self.prefix(src="*/html"):
self.path("*.png")
self.path("*/*/*.html")
self.path("*/*/*.gif")
self.end_prefix("*/html")
self.end_prefix("skins")
# local_assets dir (for pre-cached textures)
if self.prefix(src="local_assets"):
self.path("*.j2c")
self.path("*.tga")
self.end_prefix("local_assets")
# Files in the newview/ directory
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)
def login_channel(self):
"""Channel reported for login and upgrade purposes ONLY;
used for A/B testing"""
# NOTE: Do not return the normal channel if login_channel
# is not specified, as some code may branch depending on
# whether or not this is present
return self.args.get('login_channel')
def grid(self):
return self.args['grid']
def channel(self):
return self.args['channel']
def channel_unique(self):
return self.channel().replace("Second Life", "").strip()
def channel_oneword(self):
return "".join(self.channel_unique().split())
def channel_lowerword(self):
return self.channel_oneword().lower()
def icon_path(self):
icon_path="icons/"
channel_type=self.channel_lowerword()
if channel_type == 'release' \
or channel_type == 'development' \
:
icon_path += channel_type
elif channel_type == 'betaviewer' :
icon_path += 'beta'
elif re.match('project.*',channel_type) :
icon_path += 'project'
else :
icon_path += 'test'
return icon_path
def flags_list(self):
""" Convenience function that returns the command-line flags
for the grid"""
# Set command line flags relating to the target grid
grid_flags = ''
if not self.default_grid():
grid_flags = "--grid %(grid)s "\
"--helperuri http://preview-%(grid)s.secondlife.com/helpers/" %\
{'grid':self.grid()}
# set command line flags for channel
channel_flags = ''
if self.login_channel() and self.login_channel() != self.channel():
# Report a special channel during login, but use default
channel_flags = '--channel "%s"' % (self.login_channel())
elif not self.default_channel():
channel_flags = '--channel "%s"' % self.channel()
# Deal with settings
setting_flags = ''
if not self.default_channel() or not self.default_grid():
if self.default_grid():
setting_flags = '--settings settings_%s.xml'\
% self.channel_lowerword()
else:
setting_flags = '--settings settings_%s_%s.xml'\
% (self.grid(), self.channel_lowerword())
return " ".join((channel_flags, grid_flags, setting_flags)).strip()
def extract_names(self,src):
try:
contrib_file = open(src,'r')
except IOError:
print "Failed to open '%s'" % src
raise
lines = contrib_file.readlines()
contrib_file.close()
# All lines up to and including the first blank line are the file header; skip them
lines.reverse() # so that pop will pull from first to last line
while not re.match("\s*$", lines.pop()) :
pass # do nothing
# A line that starts with a non-whitespace character is a name; all others describe contributions, so collect the names
names = []
for line in lines :
if re.match("\S", line) :
names.append(line.rstrip())
# It's not fair to always put the same people at the head of the list
random.shuffle(names)
return ', '.join(names)
class WindowsManifest(ViewerManifest):
def final_exe(self):
if self.default_channel():
if self.default_grid():
return "SecondLife.exe"
else:
return "SecondLifePreview.exe"
else:
return ''.join(self.channel().split()) + '.exe'
def test_msvcrt_and_copy_action(self, src, dst):
# This is used to test a dll manifest.
# It is used as a temporary override during the construct method
from test_win32_manifest import test_assembly_binding
if src and (os.path.exists(src) or os.path.islink(src)):
# ensure that destination path exists
self.cmakedirs(os.path.dirname(dst))
self.created_paths.append(dst)
if not os.path.isdir(src):
if(self.args['configuration'].lower() == 'debug'):
test_assembly_binding(src, "Microsoft.VC80.DebugCRT", "8.0.50727.4053")
else:
test_assembly_binding(src, "Microsoft.VC80.CRT", "8.0.50727.4053")
self.ccopy(src,dst)
else:
raise Exception("Directories are not supported by test_CRT_and_copy_action()")
else:
print "Doesn't exist:", src
def test_for_no_msvcrt_manifest_and_copy_action(self, src, dst):
# This is used to test that no manifest for the msvcrt exists.
# It is used as a temporary override during the construct method
from test_win32_manifest import test_assembly_binding
from test_win32_manifest import NoManifestException, NoMatchingAssemblyException
if src and (os.path.exists(src) or os.path.islink(src)):
# ensure that destination path exists
self.cmakedirs(os.path.dirname(dst))
self.created_paths.append(dst)
if not os.path.isdir(src):
try:
if(self.args['configuration'].lower() == 'debug'):
test_assembly_binding(src, "Microsoft.VC80.DebugCRT", "")
else:
test_assembly_binding(src, "Microsoft.VC80.CRT", "")
raise Exception("Unknown condition")
except NoManifestException, err:
pass
except NoMatchingAssemblyException, err:
pass
self.ccopy(src,dst)
else:
raise Exception("Directories are not supported by test_CRT_and_copy_action()")
else:
print "Doesn't exist:", src
### DISABLED MANIFEST CHECKING for vs2010. we may need to reenable this
# shortly. If this hasn't been reenabled by the 2.9 viewer release then it
# should be deleted -brad
#def enable_crt_manifest_check(self):
# if self.is_packaging_viewer():
# WindowsManifest.copy_action = WindowsManifest.test_msvcrt_and_copy_action
#def enable_no_crt_manifest_check(self):
# if self.is_packaging_viewer():
# WindowsManifest.copy_action = WindowsManifest.test_for_no_msvcrt_manifest_and_copy_action
#def disable_manifest_check(self):
# if self.is_packaging_viewer():
# del WindowsManifest.copy_action
def construct(self):
super(WindowsManifest, self).construct()
#self.enable_crt_manifest_check()
if self.is_packaging_viewer():
# Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe.
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.disable_manifest_check()
self.path(src="../viewer_components/updater/scripts/windows/update_install.bat", dst="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=""):
#self.enable_crt_manifest_check()
# Get llcommon and deps. If missing assume static linkage and continue.
try:
self.path('llcommon.dll')
self.path('libapr-1.dll')
self.path('libaprutil-1.dll')
self.path('libapriconv-1.dll')
except RuntimeError, err:
print err.message
print "Skipping llcommon.dll (assuming llcommon was linked statically)"
#self.disable_manifest_check()
# Mesh 3rd party libs needed for auto LOD and collada reading
try:
if self.args['configuration'].lower() == 'debug':
self.path("libcollada14dom22-d.dll")
else:
self.path("libcollada14dom22.dll")
self.path("glod.dll")
except RuntimeError, err:
print err.message
print "Skipping COLLADA and GLOD libraries (assumming linked statically)"
# Get fmod dll, continue if missing
try:
self.path("fmod.dll")
except:
print "Skipping fmod.dll"
# For textures
if self.args['configuration'].lower() == 'debug':
self.path("openjpegd.dll")
else:
self.path("openjpeg.dll")
# These need to be installed as a SxS assembly, currently a 'private' assembly.
# See http://msdn.microsoft.com/en-us/library/ms235291(VS.80).aspx
if self.args['configuration'].lower() == 'debug':
self.path("msvcr100d.dll")
self.path("msvcp100d.dll")
else:
self.path("msvcr100.dll")
self.path("msvcp100.dll")
# Vivox runtimes
self.path("SLVoice.exe")
self.path("vivoxsdk.dll")
self.path("ortp.dll")
self.path("libsndfile-1.dll")
self.path("zlib1.dll")
self.path("vivoxplatform.dll")
self.path("vivoxoal.dll")
# Security
self.path("ssleay32.dll")
self.path("libeay32.dll")
# Hunspell
self.path("libhunspell.dll")
# For google-perftools tcmalloc allocator.
try:
if self.args['configuration'].lower() == 'debug':
self.path('libtcmalloc_minimal-debug.dll')
else:
self.path('libtcmalloc_minimal.dll')
except:
print "Skipping libtcmalloc_minimal.dll"
self.end_prefix()
self.path(src="licenses-win32.txt", dst="licenses.txt")
self.path("featuretable.txt")
self.path("featuretable_xp.txt")
#self.enable_no_crt_manifest_check()
# Media plugins - QuickTime
if self.prefix(src='../media_plugins/quicktime/%s' % self.args['configuration'], dst="llplugin"):
self.path("media_plugin_quicktime.dll")
self.end_prefix()
# Media plugins - WebKit/Qt
if self.prefix(src='../media_plugins/webkit/%s' % self.args['configuration'], dst="llplugin"):
self.path("media_plugin_webkit.dll")
self.end_prefix()
# winmm.dll shim
if self.prefix(src='../media_plugins/winmmshim/%s' % self.args['configuration'], dst=""):
self.path("winmm.dll")
self.end_prefix()
if self.args['configuration'].lower() == 'debug':
if self.prefix(src=os.path.join(os.pardir, 'packages', 'lib', 'debug'),
dst="llplugin"):
self.path("libeay32.dll")
self.path("qtcored4.dll")
self.path("qtguid4.dll")
self.path("qtnetworkd4.dll")
self.path("qtopengld4.dll")
self.path("qtwebkitd4.dll")
self.path("qtxmlpatternsd4.dll")
self.path("ssleay32.dll")
# For WebKit/Qt plugin runtimes (image format plugins)
if self.prefix(src="imageformats", dst="imageformats"):
self.path("qgifd4.dll")
self.path("qicod4.dll")
self.path("qjpegd4.dll")
self.path("qmngd4.dll")
self.path("qsvgd4.dll")
self.path("qtiffd4.dll")
self.end_prefix()
# For WebKit/Qt plugin runtimes (codec/character encoding plugins)
if self.prefix(src="codecs", dst="codecs"):
self.path("qcncodecsd4.dll")
self.path("qjpcodecsd4.dll")
self.path("qkrcodecsd4.dll")
self.path("qtwcodecsd4.dll")
self.end_prefix()
self.end_prefix()
else:
if self.prefix(src=os.path.join(os.pardir, 'packages', 'lib', 'release'),
dst="llplugin"):
self.path("libeay32.dll")
self.path("qtcore4.dll")
self.path("qtgui4.dll")
self.path("qtnetwork4.dll")
self.path("qtopengl4.dll")
self.path("qtwebkit4.dll")
self.path("qtxmlpatterns4.dll")
self.path("ssleay32.dll")
# For WebKit/Qt plugin runtimes (image format plugins)
if self.prefix(src="imageformats", dst="imageformats"):
self.path("qgif4.dll")
self.path("qico4.dll")
self.path("qjpeg4.dll")
self.path("qmng4.dll")
self.path("qsvg4.dll")
self.path("qtiff4.dll")
self.end_prefix()
# For WebKit/Qt plugin runtimes (codec/character encoding plugins)
if self.prefix(src="codecs", dst="codecs"):
self.path("qcncodecs4.dll")
self.path("qjpcodecs4.dll")
self.path("qkrcodecs4.dll")
self.path("qtwcodecs4.dll")
self.end_prefix()
self.end_prefix()
#self.disable_manifest_check()
# pull in the crash logger and updater from other projects
# tag:"crash-logger" here as a cue to the exporter
self.path(src='../win_crash_logger/%s/windows-crash-logger.exe' % self.args['configuration'],
dst="win_crash_logger.exe")
# For CHOP-397, windows updater no longer used.
# self.path(src='../win_updater/%s/windows-updater.exe' % self.args['configuration'],
# dst="updater.exe")
if not self.is_packaging_viewer():
self.package_file = "copied_deps"
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):
# a standard map of strings for replacing in the templates
substitution_strings = {
'version' : '.'.join(self.args['version']),
'version_short' : '.'.join(self.args['version'][:-1]),
'version_dashes' : '-'.join(self.args['version']),
'final_exe' : self.final_exe(),
'grid':self.args['grid'],
'grid_caps':self.args['grid'].upper(),
# escape quotes becase NSIS doesn't handle them well
'flags':self.flags_list().replace('"', '$\\"'),
'channel':self.channel(),
'channel_oneword':self.channel_oneword(),
'channel_unique':self.channel_unique(),
}
version_vars = """
!define INSTEXE "%(final_exe)s"
!define VERSION "%(version_short)s"
!define VERSION_LONG "%(version)s"
!define VERSION_DASHES "%(version_dashes)s"
""" % substitution_strings
if self.default_channel():
if self.default_grid():
# release viewer
installer_file = "Second_Life_%(version_dashes)s_Setup.exe"
grid_vars_template = """
OutFile "%(installer_file)s"
!define INSTFLAGS "%(flags)s"
!define INSTNAME "SecondLifeViewer"
!define SHORTCUT "Second Life Viewer"
!define URLNAME "secondlife"
Caption "Second Life"
"""
else:
# beta grid viewer
installer_file = "Second_Life_%(version_dashes)s_(%(grid_caps)s)_Setup.exe"
grid_vars_template = """
OutFile "%(installer_file)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}"
"""
else:
# some other channel on some grid
installer_file = "Second_Life_%(version_dashes)s_%(channel_oneword)s_Setup.exe"
grid_vars_template = """
OutFile "%(installer_file)s"
!define INSTFLAGS "%(flags)s"
!define INSTNAME "SecondLife%(channel_oneword)s"
!define SHORTCUT "%(channel)s"
!define URLNAME "secondlife"
!define UNINSTALL_SETTINGS 1
Caption "%(channel)s ${VERSION}"
"""
if 'installer_name' in self.args:
installer_file = self.args['installer_name']
else:
installer_file = installer_file % substitution_strings
substitution_strings['installer_file'] = installer_file
tempfile = "secondlife_setup_tmp.nsi"
# the following replaces strings in the nsi template
# it also does python-style % substitution
self.replace_in("installers/windows/installer_template.nsi", tempfile, {
"%%VERSION%%":version_vars,
"%%SOURCE%%":self.get_src_prefix(),
"%%GRID_VARS%%":grid_vars_template % substitution_strings,
"%%INSTALL_FILES%%":self.nsi_file_commands(True),
"%%DELETE_FILES%%":self.nsi_file_commands(False)})
# We use the Unicode version of NSIS, available from
# http://www.scratchpaper.com/
# Check two paths, one for Program Files, and one for Program Files (x86).
# Yay 64bit windows.
NSIS_path = os.path.expandvars('${ProgramFiles}\\NSIS\\Unicode\\makensis.exe')
if not os.path.exists(NSIS_path):
NSIS_path = os.path.expandvars('${ProgramFiles(x86)}\\NSIS\\Unicode\\makensis.exe')
self.run_command('"' + proper_windows_path(NSIS_path) + '" ' + self.dst_path_of(tempfile))
# self.remove(self.dst_path_of(tempfile))
# If we're on a build machine, sign the code using our Authenticode certificate. JC
sign_py = os.path.expandvars("${SIGN}")
if not sign_py or sign_py == "${SIGN}":
sign_py = 'C:\\buildscripts\\code-signing\\sign.py'
else:
sign_py = sign_py.replace('\\', '\\\\\\\\')
python = os.path.expandvars("${PYTHON}")
if not python or python == "${PYTHON}":
python = 'python'
if os.path.exists(sign_py):
self.run_command("%s %s %s" % (python, sign_py, self.dst_path_of(installer_file).replace('\\', '\\\\\\\\')))
else:
print "Skipping code signing,", sign_py, "does not exist"
self.created_path(self.dst_path_of(installer_file))
self.package_file = installer_file
class DarwinManifest(ViewerManifest):
def is_packaging_viewer(self):
# darwin requires full app bundle packaging even for debugging.
return True
def construct(self):
# copy over the build result (this is a no-op if run within the xcode script)
self.path(self.args['configuration'] + "/Second Life.app", dst="")
if self.prefix(src="", dst="Contents"): # everything goes in Contents
self.path("Info-SecondLife.plist", dst="Info.plist")
# copy additional libs in <bundle>/Contents/MacOS/
self.path("../packages/lib/release/libndofdev.dylib", dst="Resources/libndofdev.dylib")
self.path("../packages/lib/release/libhunspell-1.3.0.dylib", dst="Resources/libhunspell-1.3.0.dylib")
self.path("../viewer_components/updater/scripts/darwin/update_install", "MacOS/update_install")
# most everything goes in the Resources directory
if self.prefix(src="", dst="Resources"):
super(DarwinManifest, self).construct()
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.nib")
icon_path = self.icon_path()
if self.prefix(src=icon_path, dst="") :
self.path("secondlife.icns")
self.end_prefix(icon_path)
self.path("SecondLife.nib")
# Translations
self.path("English.lproj")
self.path("German.lproj")
self.path("Japanese.lproj")
self.path("Korean.lproj")
self.path("da.lproj")
self.path("es.lproj")
self.path("fr.lproj")
self.path("hu.lproj")
self.path("it.lproj")
self.path("nl.lproj")
self.path("pl.lproj")
self.path("pt.lproj")
self.path("ru.lproj")
self.path("tr.lproj")
self.path("uk.lproj")
self.path("zh-Hans.lproj")
libdir = "../packages/lib/release"
dylibs = {}
# 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)
try:
# FMOD for sound
self.path(self.args['configuration'] + "/libfmodwrapper.dylib", "libfmodwrapper.dylib")
except:
print "Skipping FMOD - not found"
# 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)}
)
# 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.end_prefix("llplugin")
# command line arguments for connecting to the proper grid
self.put_in_file(self.flags_list(), 'arguments.txt')
self.end_prefix("Resources")
self.end_prefix("Contents")
# 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.
if ("package" in self.args['actions'] or
"unpacked" in self.args['actions']):
self.run_command('strip -S %(viewer_binary)r' %
{ 'viewer_binary' : self.dst_path_of('Contents/MacOS/Second Life')})
def copy_finish(self):
# Force executable permissions to be set for scripts
# see CHOP-223 and http://mercurial.selenic.com/bts/issue1802
for script in 'Contents/MacOS/update_install',:
self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script))
def package_finish(self):
channel_standin = 'Second Life Viewer' # hah, our default channel is not usable on its own
if not self.default_channel():
channel_standin = self.channel()
imagename="SecondLife_" + '_'.join(self.args['version'])
# MBW -- If the mounted volume name changes, it breaks the .DS_Store's background image and icon positioning.
# If we really need differently named volumes, we'll need to create multiple DS_Store file images, or use some other trick.
volname="Second Life Installer" # DO NOT CHANGE without understanding comment above
if self.default_channel():
if not self.default_grid():
# beta case
imagename = imagename + '_' + self.args['grid'].upper()
else:
# first look, etc
imagename = imagename + '_' + self.channel_oneword().upper()
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)r -volname %(vol)r -fs HFS+ -type SPARSE -megabytes 700 -layout SPUD' % {
'sparse':sparsename,
'vol':volname})
# mount the image and get the name of the mount point and device node
hdi_output = self.run_command('hdiutil attach -private %r' % sparsename)
try:
devfile = re.search("/dev/disk([0-9]+)[^s]", hdi_output).group(0).strip()
volpath = re.search('HFS\s+(.+)', hdi_output).group(1).strip()
if devfile != '/dev/disk1':
# adding more debugging info based upon nat's hunches to the
# logs to help track down 'SetFile -a V' failures -brad
print "WARNING: 'SetFile -a V' command below is probably gonna fail"
# Copy everything in to the mounted .dmg
if self.default_channel() and not self.default_grid():
app_name = "Second Life " + self.args['grid']
else:
app_name = channel_standin.strip()
# Hack:
# Because there is no easy way to coerce the Finder into positioning
# the app bundle in the same place with different app names, we are
# adding multiple .DS_Store files to svn. There is one for release,
# one for release candidate and one for first look. Any other channels
# will use the release .DS_Store, and will look broken.
# - Ambroff 2008-08-20
dmg_template = os.path.join(
'installers', 'darwin', '%s-dmg' % self.channel_lowerword())
if not os.path.exists (self.src_path_of(dmg_template)):
dmg_template = os.path.join ('installers', 'darwin', 'release-dmg')
for s,d in {self.get_dst_prefix():app_name + ".app",
os.path.join(dmg_template, "_VolumeIcon.icns"): ".VolumeIcon.icns",
os.path.join(dmg_template, "background.jpg"): "background.jpg",
os.path.join(dmg_template, "_DS_Store"): ".DS_Store"}.items():
print "Copying to dmg", s, d
self.copy_action(self.src_path_of(s), os.path.join(volpath, d))
# Hide the background image, DS_Store file, and volume icon file (set their "visible" bit)
for f in ".VolumeIcon.icns", "background.jpg", ".DS_Store":
pathname = os.path.join(volpath, f)
# We've observed mysterious "no such file" failures of the SetFile
# command, especially on the first file listed above -- yet
# subsequent inspection of the target directory confirms it's
# there. Timing problem with copy command? Try to handle.
for x in xrange(3):
if os.path.exists(pathname):
print "Confirmed existence: %r" % pathname
break
print "Waiting for %s copy command to complete (%s)..." % (f, x+1)
sys.stdout.flush()
time.sleep(1)
# If we fall out of the loop above without a successful break, oh
# well, possibly we've mistaken the nature of the problem. In any
# case, don't hang up the whole build looping indefinitely, let
# the original problem manifest by executing the desired command.
self.run_command('SetFile -a V %r' % pathname)
# Create the alias file (which is a resource file) from the .r
self.run_command('Rez %r -o %r' %
(self.src_path_of("installers/darwin/release-dmg/Applications-alias.r"),
os.path.join(volpath, "Applications")))
# Set the alias file's alias and custom icon bits
self.run_command('SetFile -a AC %r' % os.path.join(volpath, "Applications"))
# Set the disk image root's custom icon bit
self.run_command('SetFile -a C %r' % volpath)
finally:
# Unmount the image even if exceptions from any of the above
self.run_command('hdiutil detach -force %r' % devfile)
print "Converting temp disk image to final disk image"
self.run_command('hdiutil convert %(sparse)r -format UDZO -imagekey zlib-level=9 -o %(final)r' % {'sparse':sparsename, 'final':finalname})
# get rid of the temp file
self.package_file = finalname
self.remove(sparsename)
class LinuxManifest(ViewerManifest):
def construct(self):
super(LinuxManifest, self).construct()
self.path("licenses-linux.txt","licenses.txt")
if self.prefix("linux_tools", dst=""):
self.path("client-readme.txt","README-linux.txt")
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")
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("res-sdl"):
self.path("*")
# recurse
self.end_prefix("res-sdl")
# Get the icons based on the channel
icon_path = self.icon_path()
if self.prefix(src=icon_path, dst="") :
self.path("secondlife_256.png","secondlife_icon.png")
if self.prefix(src="",dst="res-sdl") :
self.path("secondlife_256.BMP","ll_icon.BMP")
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.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:
print "Skipping llcommon.so (assuming llcommon was linked statically)"
self.path("featuretable_linux.txt")
def copy_finish(self):
# Force executable permissions to be set for scripts
# see CHOP-223 and http://mercurial.selenic.com/bts/issue1802
for script in 'secondlife', 'bin/update_install':
self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script))
def package_finish(self):
if 'installer_name' in self.args:
installer_name = self.args['installer_name']
else:
installer_name_components = ['SecondLife_', self.args.get('arch')]
installer_name_components.extend(self.args['version'])
installer_name = "_".join(installer_name_components)
if self.default_channel():
if not self.default_grid():
installer_name += '_' + self.args['grid'].upper()
else:
installer_name += '_' + self.channel_oneword().upper()
if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer():
print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build"
self.run_command("find %(d)r/bin %(d)r/lib -type f \\! -name update_install | xargs --no-run-if-empty strip -S" % {'d': self.get_dst_prefix()} ) # makes some small assumptions about our packaged dir structure
# Fix access permissions
self.run_command("""
find %(dst)s -type d | xargs --no-run-if-empty chmod 755;
find %(dst)s -type f -perm 0700 | xargs --no-run-if-empty chmod 0755;
find %(dst)s -type f -perm 0500 | xargs --no-run-if-empty chmod 0555;
find %(dst)s -type f -perm 0600 | xargs --no-run-if-empty chmod 0644;
find %(dst)s -type f -perm 0400 | xargs --no-run-if-empty chmod 0444;
true""" % {'dst':self.get_dst_prefix() })
self.package_file = installer_name + '.tar.bz2'
# 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.build_path_of(installer_name)})
try:
# only create tarball if it's a release build.
if self.args['buildtype'].lower() == 'release':
# --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_build_prefix(),
'inst_name': installer_name,
'inst_path':self.build_path_of(installer_name)})
else:
print "Skipping %s.tar.bz2 for non-Release build (%s)" % \
(installer_name, self.args['buildtype'])
finally:
self.run_command("mv %(inst)s %(dst)s" % {
'dst': self.get_dst_prefix(),
'inst': self.build_path_of(installer_name)})
class Linux_i686Manifest(LinuxManifest):
def construct(self):
super(Linux_i686Manifest, self).construct()
if self.prefix("../packages/lib/release", dst="lib"):
self.path("libapr-1.so*")
self.path("libaprutil-1.so*")
self.path("libbreakpad_client.so*")
self.path("libcollada14dom.so")
self.path("libdb*.so")
self.path("libcrypto.so.*")
self.path("libexpat.so.*")
self.path("libssl.so.1.0.0")
self.path("libglod.so")
self.path("libminizip.so")
self.path("libuuid.so*")
self.path("libSDL-1.2.so.*")
self.path("libdirectfb-1.*.so.*")
self.path("libfusion-1.*.so.*")
self.path("libdirect-1.*.so.*")
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("libalut.so")
self.path("libopenal.so", "libopenal.so.1")
self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname
# KLUDGE: As of 2012-04-11, the 'fontconfig' package installs
# libfontconfig.so.1.4.4, along with symlinks libfontconfig.so.1
# and libfontconfig.so. Before we added support for library-file
# wildcards, though, this self.path() call specifically named
# libfontconfig.so.1.4.4 WITHOUT also copying the symlinks. When I
# (nat) changed the call to self.path("libfontconfig.so.*"), we
# ended up with the libfontconfig.so.1 symlink in the target
# directory as well. But guess what! At least on Ubuntu 10.04,
# certain viewer fonts look terrible with libfontconfig.so.1
# present in the target directory. Removing that symlink suffices
# to improve them. I suspect that means we actually do better when
# the viewer fails to find our packaged libfontconfig.so*, falling
# back on the system one instead -- but diagnosing and fixing that
# is a bit out of scope for the present project. Meanwhile, this
# particular wildcard specification gets us exactly what the
# previous call did, without having to explicitly state the
# 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
self.end_prefix("lib")
# Vivox runtimes
if self.prefix(src="../packages/lib/release", dst="bin"):
self.path("SLVoice")
self.end_prefix()
if self.prefix(src="../packages/lib/release", dst="lib"):
self.path("libortp.so")
self.path("libsndfile.so.1")
#self.path("libvivoxoal.so.1") # no - we'll re-use the viewer's own OpenAL lib
self.path("libvivoxsdk.so")
self.path("libvivoxplatform.so")
self.end_prefix("lib")
if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer():
print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build"
self.run_command("find %(d)r/bin %(d)r/lib -type f \\! -name update_install | xargs --no-run-if-empty strip -S" % {'d': self.get_dst_prefix()} ) # makes some small assumptions about our packaged dir structure
class Linux_x86_64Manifest(LinuxManifest):
def construct(self):
super(Linux_x86_64Manifest, self).construct()
# support file for valgrind debug tool
self.path("secondlife-i686.supp")
################################################################
if __name__ == "__main__":
main()