PageRenderTime 965ms CodeModel.GetById 111ms app.highlight 662ms RepoModel.GetById 108ms app.codeStats 0ms

/indra/newview/viewer_manifest.py

https://bitbucket.org/lindenlab/viewer-beta/
Python | 1098 lines | 1024 code | 17 blank | 57 comment | 25 complexity | ae1035291460b143073982dc63e76fb7 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1#!/usr/bin/env python
   2"""\
   3@file viewer_manifest.py
   4@author Ryan Williams
   5@brief Description of all installer viewer files, and methods for packaging
   6       them into installers for all supported platforms.
   7
   8$LicenseInfo:firstyear=2006&license=viewerlgpl$
   9Second Life Viewer Source Code
  10Copyright (C) 2006-2011, Linden Research, Inc.
  11
  12This library is free software; you can redistribute it and/or
  13modify it under the terms of the GNU Lesser General Public
  14License as published by the Free Software Foundation;
  15version 2.1 of the License only.
  16
  17This library is distributed in the hope that it will be useful,
  18but WITHOUT ANY WARRANTY; without even the implied warranty of
  19MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  20Lesser General Public License for more details.
  21
  22You should have received a copy of the GNU Lesser General Public
  23License along with this library; if not, write to the Free Software
  24Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  25
  26Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  27$/LicenseInfo$
  28"""
  29import sys
  30import os.path
  31import re
  32import tarfile
  33import time
  34import random
  35viewer_dir = os.path.dirname(__file__)
  36# add llmanifest library to our path so we don't have to muck with PYTHONPATH
  37sys.path.append(os.path.join(viewer_dir, '../lib/python/indra/util'))
  38from llmanifest import LLManifest, main, proper_windows_path, path_ancestors
  39
  40class ViewerManifest(LLManifest):
  41    def is_packaging_viewer(self):
  42        # Some commands, files will only be included
  43        # if we are packaging the viewer on windows.
  44        # This manifest is also used to copy
  45        # files during the build (see copy_w_viewer_manifest
  46        # and copy_l_viewer_manifest targets)
  47        return 'package' in self.args['actions']
  48    
  49    def construct(self):
  50        super(ViewerManifest, self).construct()
  51        self.exclude("*.svn*")
  52        self.path(src="../../scripts/messages/message_template.msg", dst="app_settings/message_template.msg")
  53        self.path(src="../../etc/message.xml", dst="app_settings/message.xml")
  54
  55        if self.is_packaging_viewer():
  56            if self.prefix(src="app_settings"):
  57                self.exclude("logcontrol.xml")
  58                self.exclude("logcontrol-dev.xml")
  59                self.path("*.pem")
  60                self.path("*.ini")
  61                self.path("*.xml")
  62                self.path("*.db2")
  63
  64                # include the entire shaders directory recursively
  65                self.path("shaders")
  66                # include the extracted list of contributors
  67                contributor_names = self.extract_names("../../doc/contributions.txt")
  68                self.put_in_file(contributor_names, "contributors.txt")
  69                self.file_list.append(["../../doc/contributions.txt",self.dst_path_of("contributors.txt")])
  70                # include the extracted list of translators
  71                translator_names = self.extract_names("../../doc/translations.txt")
  72                self.put_in_file(translator_names, "translators.txt")
  73                self.file_list.append(["../../doc/translations.txt",self.dst_path_of("translators.txt")])
  74                # include the list of Lindens (if any)
  75                #   see https://wiki.lindenlab.com/wiki/Generated_Linden_Credits
  76                linden_names_path = os.getenv("LINDEN_CREDITS")
  77                if linden_names_path :
  78                    try:
  79                        linden_file = open(linden_names_path,'r')
  80                         # all names should be one line, but the join below also converts to a string
  81                        linden_names = ', '.join(linden_file.readlines())
  82                        self.put_in_file(linden_names, "lindens.txt")
  83                        linden_file.close()
  84                        print "Linden names extracted from '%s'" % linden_names_path
  85                        self.file_list.append([linden_names_path,self.dst_path_of("lindens.txt")])
  86                    except IOError:
  87                        print "No Linden names found at '%s', using built-in list" % linden_names_path
  88                        pass
  89                else :
  90                    print "No 'LINDEN_CREDITS' specified in environment, using built-in list"
  91
  92                # ... and the entire windlight directory
  93                self.path("windlight")
  94                self.end_prefix("app_settings")
  95
  96            if self.prefix(src="character"):
  97                self.path("*.llm")
  98                self.path("*.xml")
  99                self.path("*.tga")
 100                self.end_prefix("character")
 101
 102            # Include our fonts
 103            if self.prefix(src="fonts"):
 104                self.path("*.ttf")
 105                self.path("*.txt")
 106                self.end_prefix("fonts")
 107
 108            # skins
 109            if self.prefix(src="skins"):
 110                    self.path("paths.xml")
 111                    # include the entire textures directory recursively
 112                    if self.prefix(src="*/textures"):
 113                            self.path("*/*.tga")
 114                            self.path("*/*.j2c")
 115                            self.path("*/*.jpg")
 116                            self.path("*/*.png")
 117                            self.path("*.tga")
 118                            self.path("*.j2c")
 119                            self.path("*.jpg")
 120                            self.path("*.png")
 121                            self.path("textures.xml")
 122                            self.end_prefix("*/textures")
 123                    self.path("*/xui/*/*.xml")
 124                    self.path("*/xui/*/widgets/*.xml")
 125                    self.path("*/*.xml")
 126
 127                    # Local HTML files (e.g. loading screen)
 128                    if self.prefix(src="*/html"):
 129                            self.path("*.png")
 130                            self.path("*/*/*.html")
 131                            self.path("*/*/*.gif")
 132                            self.end_prefix("*/html")
 133                    self.end_prefix("skins")
 134
 135            # local_assets dir (for pre-cached textures)
 136            if self.prefix(src="local_assets"):
 137                self.path("*.j2c")
 138                self.path("*.tga")
 139                self.end_prefix("local_assets")
 140
 141            # Files in the newview/ directory
 142            self.path("gpu_table.txt")
 143
 144            # The summary.json file gets left in the base checkout dir by
 145            # build.sh. It's only created for a build.sh build, therefore we
 146            # have to check whether it exists.  :-P
 147            summary_json = "summary.json"
 148            summary_json_path = os.path.join(os.pardir, os.pardir, summary_json)
 149            if os.path.exists(os.path.join(self.get_src_prefix(), summary_json_path)):
 150                self.path(summary_json_path, summary_json)
 151            else:
 152                print "No %s" % os.path.join(self.get_src_prefix(), summary_json_path)
 153
 154    def login_channel(self):
 155        """Channel reported for login and upgrade purposes ONLY;
 156        used for A/B testing"""
 157        # NOTE: Do not return the normal channel if login_channel
 158        # is not specified, as some code may branch depending on
 159        # whether or not this is present
 160        return self.args.get('login_channel')
 161
 162    def grid(self):
 163        return self.args['grid']
 164    def channel(self):
 165        return self.args['channel']
 166    def channel_unique(self):
 167        return self.channel().replace("Second Life", "").strip()
 168    def channel_oneword(self):
 169        return "".join(self.channel_unique().split())
 170    def channel_lowerword(self):
 171        return self.channel_oneword().lower()
 172
 173    def icon_path(self):
 174        icon_path="icons/"
 175        channel_type=self.channel_lowerword()
 176        if channel_type == 'release' \
 177        or channel_type == 'development' \
 178        :
 179            icon_path += channel_type
 180        elif channel_type == 'betaviewer' :
 181            icon_path += 'beta'
 182        elif re.match('project.*',channel_type) :
 183            icon_path += 'project'
 184        else :
 185            icon_path += 'test'
 186        return icon_path
 187
 188    def flags_list(self):
 189        """ Convenience function that returns the command-line flags
 190        for the grid"""
 191
 192        # Set command line flags relating to the target grid
 193        grid_flags = ''
 194        if not self.default_grid():
 195            grid_flags = "--grid %(grid)s "\
 196                         "--helperuri http://preview-%(grid)s.secondlife.com/helpers/" %\
 197                           {'grid':self.grid()}
 198
 199        # set command line flags for channel
 200        channel_flags = ''
 201        if self.login_channel() and self.login_channel() != self.channel():
 202            # Report a special channel during login, but use default
 203            channel_flags = '--channel "%s"' % (self.login_channel())
 204        elif not self.default_channel():
 205            channel_flags = '--channel "%s"' % self.channel()
 206
 207        # Deal with settings 
 208        setting_flags = ''
 209        if not self.default_channel() or not self.default_grid():
 210            if self.default_grid():
 211                setting_flags = '--settings settings_%s.xml'\
 212                                % self.channel_lowerword()
 213            else:
 214                setting_flags = '--settings settings_%s_%s.xml'\
 215                                % (self.grid(), self.channel_lowerword())
 216                                                
 217        return " ".join((channel_flags, grid_flags, setting_flags)).strip()
 218
 219    def extract_names(self,src):
 220        try:
 221            contrib_file = open(src,'r')
 222        except IOError:
 223            print "Failed to open '%s'" % src
 224            raise
 225        lines = contrib_file.readlines()
 226        contrib_file.close()
 227
 228        # All lines up to and including the first blank line are the file header; skip them
 229        lines.reverse() # so that pop will pull from first to last line
 230        while not re.match("\s*$", lines.pop()) :
 231            pass # do nothing
 232
 233        # A line that starts with a non-whitespace character is a name; all others describe contributions, so collect the names
 234        names = []
 235        for line in lines :
 236            if re.match("\S", line) :
 237                names.append(line.rstrip())
 238        # It's not fair to always put the same people at the head of the list
 239        random.shuffle(names)
 240        return ', '.join(names)
 241
 242class WindowsManifest(ViewerManifest):
 243    def final_exe(self):
 244        if self.default_channel():
 245            if self.default_grid():
 246                return "SecondLife.exe"
 247            else:
 248                return "SecondLifePreview.exe"
 249        else:
 250            return ''.join(self.channel().split()) + '.exe'
 251
 252    def test_msvcrt_and_copy_action(self, src, dst):
 253        # This is used to test a dll manifest.
 254        # It is used as a temporary override during the construct method
 255        from test_win32_manifest import test_assembly_binding
 256        if src and (os.path.exists(src) or os.path.islink(src)):
 257            # ensure that destination path exists
 258            self.cmakedirs(os.path.dirname(dst))
 259            self.created_paths.append(dst)
 260            if not os.path.isdir(src):
 261                if(self.args['configuration'].lower() == 'debug'):
 262                    test_assembly_binding(src, "Microsoft.VC80.DebugCRT", "8.0.50727.4053")
 263                else:
 264                    test_assembly_binding(src, "Microsoft.VC80.CRT", "8.0.50727.4053")
 265                self.ccopy(src,dst)
 266            else:
 267                raise Exception("Directories are not supported by test_CRT_and_copy_action()")
 268        else:
 269            print "Doesn't exist:", src
 270
 271    def test_for_no_msvcrt_manifest_and_copy_action(self, src, dst):
 272        # This is used to test that no manifest for the msvcrt exists.
 273        # It is used as a temporary override during the construct method
 274        from test_win32_manifest import test_assembly_binding
 275        from test_win32_manifest import NoManifestException, NoMatchingAssemblyException
 276        if src and (os.path.exists(src) or os.path.islink(src)):
 277            # ensure that destination path exists
 278            self.cmakedirs(os.path.dirname(dst))
 279            self.created_paths.append(dst)
 280            if not os.path.isdir(src):
 281                try:
 282                    if(self.args['configuration'].lower() == 'debug'):
 283                        test_assembly_binding(src, "Microsoft.VC80.DebugCRT", "")
 284                    else:
 285                        test_assembly_binding(src, "Microsoft.VC80.CRT", "")
 286                    raise Exception("Unknown condition")
 287                except NoManifestException, err:
 288                    pass
 289                except NoMatchingAssemblyException, err:
 290                    pass
 291                    
 292                self.ccopy(src,dst)
 293            else:
 294                raise Exception("Directories are not supported by test_CRT_and_copy_action()")
 295        else:
 296            print "Doesn't exist:", src
 297        
 298    ### DISABLED MANIFEST CHECKING for vs2010.  we may need to reenable this
 299    # shortly.  If this hasn't been reenabled by the 2.9 viewer release then it
 300    # should be deleted -brad
 301    #def enable_crt_manifest_check(self):
 302    #    if self.is_packaging_viewer():
 303    #       WindowsManifest.copy_action = WindowsManifest.test_msvcrt_and_copy_action
 304
 305    #def enable_no_crt_manifest_check(self):
 306    #    if self.is_packaging_viewer():
 307    #        WindowsManifest.copy_action = WindowsManifest.test_for_no_msvcrt_manifest_and_copy_action
 308
 309    #def disable_manifest_check(self):
 310    #    if self.is_packaging_viewer():
 311    #        del WindowsManifest.copy_action
 312
 313    def construct(self):
 314        super(WindowsManifest, self).construct()
 315
 316        #self.enable_crt_manifest_check()
 317
 318        if self.is_packaging_viewer():
 319            # Find secondlife-bin.exe in the 'configuration' dir, then rename it to the result of final_exe.
 320            self.path(src='%s/secondlife-bin.exe' % self.args['configuration'], dst=self.final_exe())
 321
 322        # Plugin host application
 323        self.path(os.path.join(os.pardir,
 324                               'llplugin', 'slplugin', self.args['configuration'], "slplugin.exe"),
 325                  "slplugin.exe")
 326        
 327        #self.disable_manifest_check()
 328
 329        self.path(src="../viewer_components/updater/scripts/windows/update_install.bat", dst="update_install.bat")
 330        # Get shared libs from the shared libs staging directory
 331        if self.prefix(src=os.path.join(os.pardir, 'sharedlibs', self.args['configuration']),
 332                       dst=""):
 333
 334            #self.enable_crt_manifest_check()
 335            
 336            # Get llcommon and deps. If missing assume static linkage and continue.
 337            try:
 338                self.path('llcommon.dll')
 339                self.path('libapr-1.dll')
 340                self.path('libaprutil-1.dll')
 341                self.path('libapriconv-1.dll')
 342                
 343            except RuntimeError, err:
 344                print err.message
 345                print "Skipping llcommon.dll (assuming llcommon was linked statically)"
 346
 347            #self.disable_manifest_check()
 348
 349            # Mesh 3rd party libs needed for auto LOD and collada reading
 350            try:
 351                if self.args['configuration'].lower() == 'debug':
 352                    self.path("libcollada14dom22-d.dll")
 353                else:
 354                    self.path("libcollada14dom22.dll")
 355                    
 356                self.path("glod.dll")
 357            except RuntimeError, err:
 358                print err.message
 359                print "Skipping COLLADA and GLOD libraries (assumming linked statically)"
 360
 361
 362            # Get fmod dll, continue if missing
 363            try:
 364                self.path("fmod.dll")
 365            except:
 366                print "Skipping fmod.dll"
 367
 368            # For textures
 369            if self.args['configuration'].lower() == 'debug':
 370                self.path("openjpegd.dll")
 371            else:
 372                self.path("openjpeg.dll")
 373
 374            # These need to be installed as a SxS assembly, currently a 'private' assembly.
 375            # See http://msdn.microsoft.com/en-us/library/ms235291(VS.80).aspx
 376            if self.args['configuration'].lower() == 'debug':
 377                 self.path("msvcr100d.dll")
 378                 self.path("msvcp100d.dll")
 379            else:
 380                 self.path("msvcr100.dll")
 381                 self.path("msvcp100.dll")
 382
 383            # Vivox runtimes
 384            self.path("SLVoice.exe")
 385            self.path("vivoxsdk.dll")
 386            self.path("ortp.dll")
 387            self.path("libsndfile-1.dll")
 388            self.path("zlib1.dll")
 389            self.path("vivoxplatform.dll")
 390            self.path("vivoxoal.dll")
 391            
 392            # Security
 393            self.path("ssleay32.dll")
 394            self.path("libeay32.dll")
 395
 396            # For google-perftools tcmalloc allocator.
 397            try:
 398                if self.args['configuration'].lower() == 'debug':
 399                    self.path('libtcmalloc_minimal-debug.dll')
 400                else:
 401                    self.path('libtcmalloc_minimal.dll')
 402            except:
 403                print "Skipping libtcmalloc_minimal.dll"
 404
 405            self.end_prefix()
 406
 407        self.path(src="licenses-win32.txt", dst="licenses.txt")
 408        self.path("featuretable.txt")
 409        self.path("featuretable_xp.txt")
 410
 411        #self.enable_no_crt_manifest_check()
 412
 413        # Media plugins - QuickTime
 414        if self.prefix(src='../media_plugins/quicktime/%s' % self.args['configuration'], dst="llplugin"):
 415            self.path("media_plugin_quicktime.dll")
 416            self.end_prefix()
 417
 418        # Media plugins - WebKit/Qt
 419        if self.prefix(src='../media_plugins/webkit/%s' % self.args['configuration'], dst="llplugin"):
 420            self.path("media_plugin_webkit.dll")
 421            self.end_prefix()
 422
 423        # winmm.dll shim
 424        if self.prefix(src='../media_plugins/winmmshim/%s' % self.args['configuration'], dst=""):
 425            self.path("winmm.dll")
 426            self.end_prefix()
 427
 428
 429        if self.args['configuration'].lower() == 'debug':
 430            if self.prefix(src=os.path.join(os.pardir, 'packages', 'lib', 'debug'),
 431                           dst="llplugin"):
 432                self.path("libeay32.dll")
 433                self.path("qtcored4.dll")
 434                self.path("qtguid4.dll")
 435                self.path("qtnetworkd4.dll")
 436                self.path("qtopengld4.dll")
 437                self.path("qtwebkitd4.dll")
 438                self.path("qtxmlpatternsd4.dll")
 439                self.path("ssleay32.dll")
 440
 441                # For WebKit/Qt plugin runtimes (image format plugins)
 442                if self.prefix(src="imageformats", dst="imageformats"):
 443                    self.path("qgifd4.dll")
 444                    self.path("qicod4.dll")
 445                    self.path("qjpegd4.dll")
 446                    self.path("qmngd4.dll")
 447                    self.path("qsvgd4.dll")
 448                    self.path("qtiffd4.dll")
 449                    self.end_prefix()
 450
 451                # For WebKit/Qt plugin runtimes (codec/character encoding plugins)
 452                if self.prefix(src="codecs", dst="codecs"):
 453                    self.path("qcncodecsd4.dll")
 454                    self.path("qjpcodecsd4.dll")
 455                    self.path("qkrcodecsd4.dll")
 456                    self.path("qtwcodecsd4.dll")
 457                    self.end_prefix()
 458
 459                self.end_prefix()
 460        else:
 461            if self.prefix(src=os.path.join(os.pardir, 'packages', 'lib', 'release'),
 462                           dst="llplugin"):
 463                self.path("libeay32.dll")
 464                self.path("qtcore4.dll")
 465                self.path("qtgui4.dll")
 466                self.path("qtnetwork4.dll")
 467                self.path("qtopengl4.dll")
 468                self.path("qtwebkit4.dll")
 469                self.path("qtxmlpatterns4.dll")
 470                self.path("ssleay32.dll")
 471
 472                # For WebKit/Qt plugin runtimes (image format plugins)
 473                if self.prefix(src="imageformats", dst="imageformats"):
 474                    self.path("qgif4.dll")
 475                    self.path("qico4.dll")
 476                    self.path("qjpeg4.dll")
 477                    self.path("qmng4.dll")
 478                    self.path("qsvg4.dll")
 479                    self.path("qtiff4.dll")
 480                    self.end_prefix()
 481
 482                # For WebKit/Qt plugin runtimes (codec/character encoding plugins)
 483                if self.prefix(src="codecs", dst="codecs"):
 484                    self.path("qcncodecs4.dll")
 485                    self.path("qjpcodecs4.dll")
 486                    self.path("qkrcodecs4.dll")
 487                    self.path("qtwcodecs4.dll")
 488                    self.end_prefix()
 489
 490                self.end_prefix()
 491
 492        #self.disable_manifest_check()
 493
 494        # pull in the crash logger and updater from other projects
 495        # tag:"crash-logger" here as a cue to the exporter
 496        self.path(src='../win_crash_logger/%s/windows-crash-logger.exe' % self.args['configuration'],
 497                  dst="win_crash_logger.exe")
 498# For CHOP-397, windows updater no longer used.
 499#        self.path(src='../win_updater/%s/windows-updater.exe' % self.args['configuration'],
 500#                  dst="updater.exe")
 501
 502        if not self.is_packaging_viewer():
 503            self.package_file = "copied_deps"    
 504
 505    def nsi_file_commands(self, install=True):
 506        def wpath(path):
 507            if path.endswith('/') or path.endswith(os.path.sep):
 508                path = path[:-1]
 509            path = path.replace('/', '\\')
 510            return path
 511
 512        result = ""
 513        dest_files = [pair[1] for pair in self.file_list if pair[0] and os.path.isfile(pair[1])]
 514        # sort deepest hierarchy first
 515        dest_files.sort(lambda a,b: cmp(a.count(os.path.sep),b.count(os.path.sep)) or cmp(a,b))
 516        dest_files.reverse()
 517        out_path = None
 518        for pkg_file in dest_files:
 519            rel_file = os.path.normpath(pkg_file.replace(self.get_dst_prefix()+os.path.sep,''))
 520            installed_dir = wpath(os.path.join('$INSTDIR', os.path.dirname(rel_file)))
 521            pkg_file = wpath(os.path.normpath(pkg_file))
 522            if installed_dir != out_path:
 523                if install:
 524                    out_path = installed_dir
 525                    result += 'SetOutPath ' + out_path + '\n'
 526            if install:
 527                result += 'File ' + pkg_file + '\n'
 528            else:
 529                result += 'Delete ' + wpath(os.path.join('$INSTDIR', rel_file)) + '\n'
 530        # at the end of a delete, just rmdir all the directories
 531        if not install:
 532            deleted_file_dirs = [os.path.dirname(pair[1].replace(self.get_dst_prefix()+os.path.sep,'')) for pair in self.file_list]
 533            # find all ancestors so that we don't skip any dirs that happened to have no non-dir children
 534            deleted_dirs = []
 535            for d in deleted_file_dirs:
 536                deleted_dirs.extend(path_ancestors(d))
 537            # sort deepest hierarchy first
 538            deleted_dirs.sort(lambda a,b: cmp(a.count(os.path.sep),b.count(os.path.sep)) or cmp(a,b))
 539            deleted_dirs.reverse()
 540            prev = None
 541            for d in deleted_dirs:
 542                if d != prev:   # skip duplicates
 543                    result += 'RMDir ' + wpath(os.path.join('$INSTDIR', os.path.normpath(d))) + '\n'
 544                prev = d
 545
 546        return result
 547
 548    def package_finish(self):
 549        # a standard map of strings for replacing in the templates
 550        substitution_strings = {
 551            'version' : '.'.join(self.args['version']),
 552            'version_short' : '.'.join(self.args['version'][:-1]),
 553            'version_dashes' : '-'.join(self.args['version']),
 554            'final_exe' : self.final_exe(),
 555            'grid':self.args['grid'],
 556            'grid_caps':self.args['grid'].upper(),
 557            # escape quotes becase NSIS doesn't handle them well
 558            'flags':self.flags_list().replace('"', '$\\"'),
 559            'channel':self.channel(),
 560            'channel_oneword':self.channel_oneword(),
 561            'channel_unique':self.channel_unique(),
 562            }
 563
 564        version_vars = """
 565        !define INSTEXE  "%(final_exe)s"
 566        !define VERSION "%(version_short)s"
 567        !define VERSION_LONG "%(version)s"
 568        !define VERSION_DASHES "%(version_dashes)s"
 569        """ % substitution_strings
 570        if self.default_channel():
 571            if self.default_grid():
 572                # release viewer
 573                installer_file = "Second_Life_%(version_dashes)s_Setup.exe"
 574                grid_vars_template = """
 575                OutFile "%(installer_file)s"
 576                !define INSTFLAGS "%(flags)s"
 577                !define INSTNAME   "SecondLifeViewer"
 578                !define SHORTCUT   "Second Life Viewer"
 579                !define URLNAME   "secondlife"
 580                Caption "Second Life"
 581                """
 582            else:
 583                # beta grid viewer
 584                installer_file = "Second_Life_%(version_dashes)s_(%(grid_caps)s)_Setup.exe"
 585                grid_vars_template = """
 586                OutFile "%(installer_file)s"
 587                !define INSTFLAGS "%(flags)s"
 588                !define INSTNAME   "SecondLife%(grid_caps)s"
 589                !define SHORTCUT   "Second Life (%(grid_caps)s)"
 590                !define URLNAME   "secondlife%(grid)s"
 591                !define UNINSTALL_SETTINGS 1
 592                Caption "Second Life %(grid)s ${VERSION}"
 593                """
 594        else:
 595            # some other channel on some grid
 596            installer_file = "Second_Life_%(version_dashes)s_%(channel_oneword)s_Setup.exe"
 597            grid_vars_template = """
 598            OutFile "%(installer_file)s"
 599            !define INSTFLAGS "%(flags)s"
 600            !define INSTNAME   "SecondLife%(channel_oneword)s"
 601            !define SHORTCUT   "%(channel)s"
 602            !define URLNAME   "secondlife"
 603            !define UNINSTALL_SETTINGS 1
 604            Caption "%(channel)s ${VERSION}"
 605            """
 606        if 'installer_name' in self.args:
 607            installer_file = self.args['installer_name']
 608        else:
 609            installer_file = installer_file % substitution_strings
 610        substitution_strings['installer_file'] = installer_file
 611
 612        tempfile = "secondlife_setup_tmp.nsi"
 613        # the following replaces strings in the nsi template
 614        # it also does python-style % substitution
 615        self.replace_in("installers/windows/installer_template.nsi", tempfile, {
 616                "%%VERSION%%":version_vars,
 617                "%%SOURCE%%":self.get_src_prefix(),
 618                "%%GRID_VARS%%":grid_vars_template % substitution_strings,
 619                "%%INSTALL_FILES%%":self.nsi_file_commands(True),
 620                "%%DELETE_FILES%%":self.nsi_file_commands(False)})
 621
 622        # We use the Unicode version of NSIS, available from
 623        # http://www.scratchpaper.com/
 624        # Check two paths, one for Program Files, and one for Program Files (x86).
 625        # Yay 64bit windows.
 626        NSIS_path = os.path.expandvars('${ProgramFiles}\\NSIS\\Unicode\\makensis.exe')
 627        if not os.path.exists(NSIS_path):
 628            NSIS_path = os.path.expandvars('${ProgramFiles(x86)}\\NSIS\\Unicode\\makensis.exe')
 629        self.run_command('"' + proper_windows_path(NSIS_path) + '" ' + self.dst_path_of(tempfile))
 630        # self.remove(self.dst_path_of(tempfile))
 631        # If we're on a build machine, sign the code using our Authenticode certificate. JC
 632        sign_py = os.path.expandvars("${SIGN}")
 633        if not sign_py or sign_py == "${SIGN}":
 634            sign_py = 'C:\\buildscripts\\code-signing\\sign.py'
 635        else:
 636            sign_py = sign_py.replace('\\', '\\\\\\\\')
 637        python = os.path.expandvars("${PYTHON}")
 638        if not python or python == "${PYTHON}":
 639            python = 'python'
 640        if os.path.exists(sign_py):
 641            self.run_command("%s %s %s" % (python, sign_py, self.dst_path_of(installer_file).replace('\\', '\\\\\\\\')))
 642        else:
 643            print "Skipping code signing,", sign_py, "does not exist"
 644        self.created_path(self.dst_path_of(installer_file))
 645        self.package_file = installer_file
 646
 647
 648class DarwinManifest(ViewerManifest):
 649    def is_packaging_viewer(self):
 650        # darwin requires full app bundle packaging even for debugging.
 651        return True
 652
 653    def construct(self):
 654        # copy over the build result (this is a no-op if run within the xcode script)
 655        self.path(self.args['configuration'] + "/Second Life.app", dst="")
 656
 657        if self.prefix(src="", dst="Contents"):  # everything goes in Contents
 658            self.path("Info-SecondLife.plist", dst="Info.plist")
 659
 660            # copy additional libs in <bundle>/Contents/MacOS/
 661            self.path("../packages/lib/release/libndofdev.dylib", dst="Resources/libndofdev.dylib")
 662
 663            self.path("../viewer_components/updater/scripts/darwin/update_install", "MacOS/update_install")
 664
 665            # most everything goes in the Resources directory
 666            if self.prefix(src="", dst="Resources"):
 667                super(DarwinManifest, self).construct()
 668
 669                if self.prefix("cursors_mac"):
 670                    self.path("*.tif")
 671                    self.end_prefix("cursors_mac")
 672
 673                self.path("licenses-mac.txt", dst="licenses.txt")
 674                self.path("featuretable_mac.txt")
 675                self.path("SecondLife.nib")
 676
 677                icon_path = self.icon_path()
 678                if self.prefix(src=icon_path, dst="") :
 679                    self.path("secondlife.icns")
 680                    self.end_prefix(icon_path)
 681
 682                self.path("SecondLife.nib")
 683                
 684                # Translations
 685                self.path("English.lproj")
 686                self.path("German.lproj")
 687                self.path("Japanese.lproj")
 688                self.path("Korean.lproj")
 689                self.path("da.lproj")
 690                self.path("es.lproj")
 691                self.path("fr.lproj")
 692                self.path("hu.lproj")
 693                self.path("it.lproj")
 694                self.path("nl.lproj")
 695                self.path("pl.lproj")
 696                self.path("pt.lproj")
 697                self.path("ru.lproj")
 698                self.path("tr.lproj")
 699                self.path("uk.lproj")
 700                self.path("zh-Hans.lproj")
 701
 702                libdir = "../packages/lib/release"
 703                dylibs = {}
 704
 705                # Need to get the llcommon dll from any of the build directories as well
 706                lib = "llcommon"
 707                libfile = "lib%s.dylib" % lib
 708                try:
 709                    self.path(self.find_existing_file(os.path.join(os.pardir,
 710                                                                    lib,
 711                                                                    self.args['configuration'],
 712                                                                    libfile),
 713                                                      os.path.join(libdir, libfile)),
 714                                                      dst=libfile)
 715                except RuntimeError:
 716                    print "Skipping %s" % libfile
 717                    dylibs[lib] = False
 718                else:
 719                    dylibs[lib] = True
 720
 721                if dylibs["llcommon"]:
 722                    for libfile in ("libapr-1.0.dylib",
 723                                    "libaprutil-1.0.dylib",
 724                                    "libexpat.1.5.2.dylib",
 725                                    "libexception_handler.dylib",
 726                                    "libGLOD.dylib",
 727                                    "libcollada14dom.dylib"
 728                                    ):
 729                        self.path(os.path.join(libdir, libfile), libfile)
 730
 731                # SLVoice and vivox lols
 732                for libfile in ('libsndfile.dylib', 'libvivoxoal.dylib', 'libortp.dylib', \
 733                    'libvivoxsdk.dylib', 'libvivoxplatform.dylib', 'SLVoice') :
 734                     self.path(os.path.join(libdir, libfile), libfile)
 735                
 736                try:
 737                    # FMOD for sound
 738                    self.path(self.args['configuration'] + "/libfmodwrapper.dylib", "libfmodwrapper.dylib")
 739                except:
 740                    print "Skipping FMOD - not found"
 741                
 742                # our apps
 743                self.path("../mac_crash_logger/" + self.args['configuration'] + "/mac-crash-logger.app", "mac-crash-logger.app")
 744                self.path("../mac_updater/" + self.args['configuration'] + "/mac-updater.app", "mac-updater.app")
 745
 746                # plugin launcher
 747                self.path("../llplugin/slplugin/" + self.args['configuration'] + "/SLPlugin.app", "SLPlugin.app")
 748
 749                # our apps dependencies on shared libs
 750                if dylibs["llcommon"]:
 751                    mac_crash_logger_res_path = self.dst_path_of("mac-crash-logger.app/Contents/Resources")
 752                    mac_updater_res_path = self.dst_path_of("mac-updater.app/Contents/Resources")
 753                    slplugin_res_path = self.dst_path_of("SLPlugin.app/Contents/Resources")
 754                    for libfile in ("libllcommon.dylib",
 755                                    "libapr-1.0.dylib",
 756                                    "libaprutil-1.0.dylib",
 757                                    "libexpat.1.5.2.dylib",
 758                                    "libexception_handler.dylib",
 759                                    "libGLOD.dylib",
 760                                    "libcollada14dom.dylib"
 761                                    ):
 762                        target_lib = os.path.join('../../..', libfile)
 763                        self.run_command("ln -sf %(target)r %(link)r" % 
 764                                         {'target': target_lib,
 765                                          'link' : os.path.join(mac_crash_logger_res_path, libfile)}
 766                                         )
 767                        self.run_command("ln -sf %(target)r %(link)r" % 
 768                                         {'target': target_lib,
 769                                          'link' : os.path.join(mac_updater_res_path, libfile)}
 770                                         )
 771                        self.run_command("ln -sf %(target)r %(link)r" % 
 772                                         {'target': target_lib,
 773                                          'link' : os.path.join(slplugin_res_path, libfile)}
 774                                         )
 775
 776                # plugins
 777                if self.prefix(src="", dst="llplugin"):
 778                    self.path("../media_plugins/quicktime/" + self.args['configuration'] + "/media_plugin_quicktime.dylib", "media_plugin_quicktime.dylib")
 779                    self.path("../media_plugins/webkit/" + self.args['configuration'] + "/media_plugin_webkit.dylib", "media_plugin_webkit.dylib")
 780                    self.path("../packages/lib/release/libllqtwebkit.dylib", "libllqtwebkit.dylib")
 781
 782                    self.end_prefix("llplugin")
 783
 784                # command line arguments for connecting to the proper grid
 785                self.put_in_file(self.flags_list(), 'arguments.txt')
 786
 787                self.end_prefix("Resources")
 788
 789            self.end_prefix("Contents")
 790
 791        # NOTE: the -S argument to strip causes it to keep enough info for
 792        # annotated backtraces (i.e. function names in the crash log).  'strip' with no
 793        # arguments yields a slightly smaller binary but makes crash logs mostly useless.
 794        # This may be desirable for the final release.  Or not.
 795        if ("package" in self.args['actions'] or 
 796            "unpacked" in self.args['actions']):
 797            self.run_command('strip -S %(viewer_binary)r' %
 798                             { 'viewer_binary' : self.dst_path_of('Contents/MacOS/Second Life')})
 799
 800
 801    def copy_finish(self):
 802        # Force executable permissions to be set for scripts
 803        # see CHOP-223 and http://mercurial.selenic.com/bts/issue1802
 804        for script in 'Contents/MacOS/update_install',:
 805            self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script))
 806
 807    def package_finish(self):
 808        channel_standin = 'Second Life Viewer'  # hah, our default channel is not usable on its own
 809        if not self.default_channel():
 810            channel_standin = self.channel()
 811
 812        imagename="SecondLife_" + '_'.join(self.args['version'])
 813
 814        # MBW -- If the mounted volume name changes, it breaks the .DS_Store's background image and icon positioning.
 815        #  If we really need differently named volumes, we'll need to create multiple DS_Store file images, or use some other trick.
 816
 817        volname="Second Life Installer"  # DO NOT CHANGE without understanding comment above
 818
 819        if self.default_channel():
 820            if not self.default_grid():
 821                # beta case
 822                imagename = imagename + '_' + self.args['grid'].upper()
 823        else:
 824            # first look, etc
 825            imagename = imagename + '_' + self.channel_oneword().upper()
 826
 827        sparsename = imagename + ".sparseimage"
 828        finalname = imagename + ".dmg"
 829        # make sure we don't have stale files laying about
 830        self.remove(sparsename, finalname)
 831
 832        self.run_command('hdiutil create %(sparse)r -volname %(vol)r -fs HFS+ -type SPARSE -megabytes 700 -layout SPUD' % {
 833                'sparse':sparsename,
 834                'vol':volname})
 835
 836        # mount the image and get the name of the mount point and device node
 837        hdi_output = self.run_command('hdiutil attach -private %r' % sparsename)
 838        try:
 839            devfile = re.search("/dev/disk([0-9]+)[^s]", hdi_output).group(0).strip()
 840            volpath = re.search('HFS\s+(.+)', hdi_output).group(1).strip()
 841
 842            if devfile != '/dev/disk1':
 843                # adding more debugging info based upon nat's hunches to the
 844                # logs to help track down 'SetFile -a V' failures -brad
 845                print "WARNING: 'SetFile -a V' command below is probably gonna fail"
 846
 847            # Copy everything in to the mounted .dmg
 848
 849            if self.default_channel() and not self.default_grid():
 850                app_name = "Second Life " + self.args['grid']
 851            else:
 852                app_name = channel_standin.strip()
 853
 854            # Hack:
 855            # Because there is no easy way to coerce the Finder into positioning
 856            # the app bundle in the same place with different app names, we are
 857            # adding multiple .DS_Store files to svn. There is one for release,
 858            # one for release candidate and one for first look. Any other channels
 859            # will use the release .DS_Store, and will look broken.
 860            # - Ambroff 2008-08-20
 861            dmg_template = os.path.join(
 862                'installers', 'darwin', '%s-dmg' % self.channel_lowerword())
 863
 864            if not os.path.exists (self.src_path_of(dmg_template)):
 865                dmg_template = os.path.join ('installers', 'darwin', 'release-dmg')
 866
 867            for s,d in {self.get_dst_prefix():app_name + ".app",
 868                        os.path.join(dmg_template, "_VolumeIcon.icns"): ".VolumeIcon.icns",
 869                        os.path.join(dmg_template, "background.jpg"): "background.jpg",
 870                        os.path.join(dmg_template, "_DS_Store"): ".DS_Store"}.items():
 871                print "Copying to dmg", s, d
 872                self.copy_action(self.src_path_of(s), os.path.join(volpath, d))
 873
 874            # Hide the background image, DS_Store file, and volume icon file (set their "visible" bit)
 875            for f in ".VolumeIcon.icns", "background.jpg", ".DS_Store":
 876                pathname = os.path.join(volpath, f)
 877                # We've observed mysterious "no such file" failures of the SetFile
 878                # command, especially on the first file listed above -- yet
 879                # subsequent inspection of the target directory confirms it's
 880                # there. Timing problem with copy command? Try to handle.
 881                for x in xrange(3):
 882                    if os.path.exists(pathname):
 883                        print "Confirmed existence: %r" % pathname
 884                        break
 885                    print "Waiting for %s copy command to complete (%s)..." % (f, x+1)
 886                    sys.stdout.flush()
 887                    time.sleep(1)
 888                # If we fall out of the loop above without a successful break, oh
 889                # well, possibly we've mistaken the nature of the problem. In any
 890                # case, don't hang up the whole build looping indefinitely, let
 891                # the original problem manifest by executing the desired command.
 892                self.run_command('SetFile -a V %r' % pathname)
 893
 894            # Create the alias file (which is a resource file) from the .r
 895            self.run_command('Rez %r -o %r' %
 896                             (self.src_path_of("installers/darwin/release-dmg/Applications-alias.r"),
 897                              os.path.join(volpath, "Applications")))
 898
 899            # Set the alias file's alias and custom icon bits
 900            self.run_command('SetFile -a AC %r' % os.path.join(volpath, "Applications"))
 901
 902            # Set the disk image root's custom icon bit
 903            self.run_command('SetFile -a C %r' % volpath)
 904        finally:
 905            # Unmount the image even if exceptions from any of the above 
 906            self.run_command('hdiutil detach -force %r' % devfile)
 907
 908        print "Converting temp disk image to final disk image"
 909        self.run_command('hdiutil convert %(sparse)r -format UDZO -imagekey zlib-level=9 -o %(final)r' % {'sparse':sparsename, 'final':finalname})
 910        # get rid of the temp file
 911        self.package_file = finalname
 912        self.remove(sparsename)
 913
 914class LinuxManifest(ViewerManifest):
 915    def construct(self):
 916        super(LinuxManifest, self).construct()
 917        self.path("licenses-linux.txt","licenses.txt")
 918        if self.prefix("linux_tools", dst=""):
 919            self.path("client-readme.txt","README-linux.txt")
 920            self.path("client-readme-voice.txt","README-linux-voice.txt")
 921            self.path("client-readme-joystick.txt","README-linux-joystick.txt")
 922            self.path("wrapper.sh","secondlife")
 923            self.path("handle_secondlifeprotocol.sh", "etc/handle_secondlifeprotocol.sh")
 924            self.path("register_secondlifeprotocol.sh", "etc/register_secondlifeprotocol.sh")
 925            self.path("refresh_desktop_app_entry.sh", "etc/refresh_desktop_app_entry.sh")
 926            self.path("launch_url.sh","etc/launch_url.sh")
 927            self.path("install.sh")
 928            self.end_prefix("linux_tools")
 929
 930        # Create an appropriate gridargs.dat for this package, denoting required grid.
 931        self.put_in_file(self.flags_list(), 'etc/gridargs.dat')
 932
 933        self.path("secondlife-bin","bin/do-not-directly-run-secondlife-bin")
 934        self.path("../linux_crash_logger/linux-crash-logger","bin/linux-crash-logger.bin")
 935        self.path("../linux_updater/linux-updater", "bin/linux-updater.bin")
 936        self.path("../llplugin/slplugin/SLPlugin", "bin/SLPlugin")
 937
 938        if self.prefix("res-sdl"):
 939            self.path("*")
 940            # recurse
 941            self.end_prefix("res-sdl")
 942
 943        # Get the icons based on the channel
 944        icon_path = self.icon_path()
 945        if self.prefix(src=icon_path, dst="") :
 946            self.path("secondlife_256.png","secondlife_icon.png")
 947            if self.prefix(src="",dst="res-sdl") :
 948                self.path("secondlife_256.BMP","ll_icon.BMP")
 949                self.end_prefix("res-sdl")
 950            self.end_prefix(icon_path)
 951
 952        self.path("../viewer_components/updater/scripts/linux/update_install", "bin/update_install")
 953
 954        # plugins
 955        if self.prefix(src="", dst="bin/llplugin"):
 956            self.path("../media_plugins/webkit/libmedia_plugin_webkit.so", "libmedia_plugin_webkit.so")
 957            self.path("../media_plugins/gstreamer010/libmedia_plugin_gstreamer010.so", "libmedia_plugin_gstreamer.so")
 958            self.end_prefix("bin/llplugin")
 959
 960        try:
 961            self.path("../llcommon/libllcommon.so", "lib/libllcommon.so")
 962        except:
 963            print "Skipping llcommon.so (assuming llcommon was linked statically)"
 964
 965        self.path("featuretable_linux.txt")
 966
 967    def copy_finish(self):
 968        # Force executable permissions to be set for scripts
 969        # see CHOP-223 and http://mercurial.selenic.com/bts/issue1802
 970        for script in 'secondlife', 'bin/update_install':
 971            self.run_command("chmod +x %r" % os.path.join(self.get_dst_prefix(), script))
 972
 973    def package_finish(self):
 974        if 'installer_name' in self.args:
 975            installer_name = self.args['installer_name']
 976        else:
 977            installer_name_components = ['SecondLife_', self.args.get('arch')]
 978            installer_name_components.extend(self.args['version'])
 979            installer_name = "_".join(installer_name_components)
 980            if self.default_channel():
 981                if not self.default_grid():
 982                    installer_name += '_' + self.args['grid'].upper()
 983            else:
 984                installer_name += '_' + self.channel_oneword().upper()
 985
 986        if self.args['buildtype'].lower() == 'release' and self.is_packaging_viewer():
 987            print "* Going strip-crazy on the packaged binaries, since this is a RELEASE build"
 988            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
 989
 990        # Fix access permissions
 991        self.run_command("""
 992                find %(dst)s -type d | xargs --no-run-if-empty chmod 755;
 993                find %(dst)s -type f -perm 0700 | xargs --no-run-if-empty chmod 0755;
 994                find %(dst)s -type f -perm 0500 | xargs --no-run-if-empty chmod 0555;
 995                find %(dst)s -type f -perm 0600 | xargs --no-run-if-empty chmod 0644;
 996                find %(dst)s -type f -perm 0400 | xargs --no-run-if-empty chmod 0444;
 997                true""" %  {'dst':self.get_dst_prefix() })
 998        self.package_file = installer_name + '.tar.bz2'
 999
1000        # temporarily move directory tree so that it has the right
1001        # name in the tarfile
1002        self.run_command("mv %(dst)s %(inst)s" % {
1003            'dst': self.get_dst_prefix(),
1004            'inst': self.build_path_of(installer_name)})
1005        try:
1006            # only create tarball if it's a release build.
1007            if self.args['buildtype'].lower() == 'release':
1008                # --numeric-owner hides the username of the builder for
1009                # security etc.
1010                self.run_command('tar -C %(dir)s --numeric-owner -cjf '
1011                                 '%(inst_path)s.tar.bz2 %(inst_name)s' % {
1012                        'dir': self.get_build_prefix(),
1013                        'inst_name': installer_name,
1014                        'inst_path':self.build_path_of(installer_name)})
1015            else:
1016                print "Skipping %s.tar.bz2 for non-Release build (%s)" % \
1017                      (installer_name, self.args['buildtype'])
1018        finally:
1019            self.run_command("mv %(inst)s %(dst)s" % {
1020                'dst': self.get_dst_prefix(),
1021                'inst': self.build_path_of(installer_name)})
1022
1023class Linux_i686Manifest(LinuxManifest):
1024    def construct(self):
1025        super(Linux_i686Manifest, self).construct()
1026
1027        if self.prefix("../packages/lib/release", dst="lib"):
1028            self.path("libapr-1.so")
1029            self.path("libapr-1.so.0")
1030            self.path("libapr-1.so.0.4.2")
1031            self.path("libaprutil-1.so")
1032            self.path("libaprutil-1.so.0")
1033            self.path("libaprutil-1.so.0.3.10")
1034            self.path("libbreakpad_client.so.0.0.0")
1035            self.path("libbreakpad_client.so.0")
1036            self.path("libbreakpad_client.so")
1037            self.path("libcollada14dom.so")
1038            self.path("libdb-5.1.so")
1039            self.path("libdb-5.so")
1040            self.path("libdb.so")
1041            self.path("libcrypto.so.1.0.0")
1042            self.path("libexpat.so.1.5.2")
1043            self.path("libssl.so.1.0.0")
1044            self.path("libglod.so")
1045            self.path("libminizip.so")
1046            self.path("libuuid.so")
1047            self.path("libuuid.so.16")
1048            self.path("libuuid.so.16.0.22")
1049            self.path("libSDL-1.2.so.0.11.3")
1050            self.path("libdirectfb-1.4.so.5.0.4")
1051            self.path("libfusion-1.4.so.5.0.4")
1052            self.path("libdirect-1.4.so.5.0.4")
1053            self.path("libopenjpeg.so.1.4.0")
1054            self.path("libopenjpeg.so.1")
1055            self.path("libopenjpeg.so")
1056            self.path("libalut.so")
1057            self.path("libopenal.so", "libopenal.so.1")
1058            self.path("libopenal.so", "libvivoxoal.so.1") # vivox's sdk expects this soname
1059            self.path("libfontconfig.so.1.4.4")
1060            self.path("libtcmalloc.so", "libtcmalloc.so") #formerly called google perf tools
1061            self.path("libtcmalloc.so.0", "libtcmalloc.so.0") #formerly called google perf tools
1062            self.path("libtcmalloc.so.0.1.0", "libtcmalloc.so.0.1.0") #formerly called google perf tools
1063            try:
1064                    self.path("libfmod-3.75.so")
1065                    pass
1066            except:
1067                    print "Skipping libfmod-3.75.so - not found"
1068                    pass
1069            self.end_prefix("lib")
1070
1071            # Vivox runtimes
1072            if self.prefix(src="../packages/lib/release", dst="bin"):
1073                    self.path("SLVoice")
1074                    self.end_prefix()
1075            if self.prefix(src="../packages/lib/release", dst="lib"):
1076                    self.path("libortp.so")
1077                    self.path("libsndfile.so.1")
1078                    #self.path("libvivoxoal.so.1") # no - we'll re-use …

Large files files are truncated, but you can click here to view the full file