diff options
Diffstat (limited to 'indra')
96 files changed, 2747 insertions, 1357 deletions
| diff --git a/indra/CMakeLists.txt b/indra/CMakeLists.txt index a40b2c0846..6c20a813ba 100644 --- a/indra/CMakeLists.txt +++ b/indra/CMakeLists.txt @@ -44,6 +44,7 @@ if (WINDOWS AND EXISTS ${LIBS_CLOSED_DIR}copy_win_scripts)  endif (WINDOWS AND EXISTS ${LIBS_CLOSED_DIR}copy_win_scripts)  add_custom_target(viewer) +  add_subdirectory(${LIBS_OPEN_PREFIX}llcrashlogger)  add_subdirectory(${LIBS_OPEN_PREFIX}llplugin)  add_subdirectory(${LIBS_OPEN_PREFIX}llui) diff --git a/indra/cmake/CMakeLists.txt b/indra/cmake/CMakeLists.txt index 4a3ebe4835..84e1c5d6fd 100644 --- a/indra/cmake/CMakeLists.txt +++ b/indra/cmake/CMakeLists.txt @@ -12,6 +12,7 @@ set(cmake_SOURCE_FILES      Audio.cmake      BerkeleyDB.cmake      Boost.cmake +    bugsplat.cmake      BuildVersion.cmake      CEFPlugin.cmake      CEFPlugin.cmake diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake index 09a97fc03e..dde53835fb 100644 --- a/indra/cmake/Copy3rdPartyLibs.cmake +++ b/indra/cmake/Copy3rdPartyLibs.cmake @@ -49,6 +49,20 @@ if(WINDOWS)          libhunspell.dll          ) +    # Filenames are different for 32/64 bit BugSplat file and we don't +    # have any control over them so need to branch. +    if (BUGSPLAT_DB) +      if(ADDRESS_SIZE EQUAL 32) +        set(release_files ${release_files} BugSplat.dll) +        set(release_files ${release_files} BugSplatRc.dll) +        set(release_files ${release_files} BsSndRpt.exe) +      else(ADDRESS_SIZE EQUAL 32) +        set(release_files ${release_files} BugSplat64.dll) +        set(release_files ${release_files} BugSplatRc64.dll) +        set(release_files ${release_files} BsSndRpt64.exe) +      endif(ADDRESS_SIZE EQUAL 32) +    endif (BUGSPLAT_DB) +      if (FMODEX)          if(ADDRESS_SIZE EQUAL 32) diff --git a/indra/cmake/LLAddBuildTest.cmake b/indra/cmake/LLAddBuildTest.cmake index 024bfe14a1..b3f42c1a5e 100644 --- a/indra/cmake/LLAddBuildTest.cmake +++ b/indra/cmake/LLAddBuildTest.cmake @@ -1,4 +1,5 @@  # -*- cmake -*- +include(00-Common)  include(LLTestCommand)  include(GoogleMock)  include(Tut) diff --git a/indra/cmake/LLBase.cmake b/indra/cmake/LLBase.cmake deleted file mode 100644 index 76e3c688a3..0000000000 --- a/indra/cmake/LLBase.cmake +++ /dev/null @@ -1,4 +0,0 @@ -# -*- cmake -*- -include(Prebuilt) - -use_prebuilt_binary(llbase) diff --git a/indra/cmake/Linking.cmake b/indra/cmake/Linking.cmake index 74fe3f1137..3cb235a9d5 100644 --- a/indra/cmake/Linking.cmake +++ b/indra/cmake/Linking.cmake @@ -66,6 +66,7 @@ if (WINDOWS)        wldap32        gdi32        user32 +      ole32        dbghelp        )  else (WINDOWS) diff --git a/indra/cmake/Requests.cmake b/indra/cmake/Requests.cmake deleted file mode 100644 index b9c729d697..0000000000 --- a/indra/cmake/Requests.cmake +++ /dev/null @@ -1,7 +0,0 @@ -if (DARWIN) -   include (Prebuilt) -   use_prebuilt_binary(requests) -   use_prebuilt_binary(urllib3) -   use_prebuilt_binary(chardet) -   use_prebuilt_binary(idna) -endif (DARWIN) diff --git a/indra/cmake/Variables.cmake b/indra/cmake/Variables.cmake index bd69c49856..2b54cd4155 100644 --- a/indra/cmake/Variables.cmake +++ b/indra/cmake/Variables.cmake @@ -33,6 +33,8 @@ set(INTEGRATION_TESTS_PREFIX)  set(LL_TESTS ON CACHE BOOL "Build and run unit and integration tests (disable for build timing runs to reduce variation")  set(INCREMENTAL_LINK OFF CACHE BOOL "Use incremental linking on win32 builds (enable for faster links on some machines)")  set(ENABLE_MEDIA_PLUGINS ON CACHE BOOL "Turn off building media plugins if they are imported by third-party library mechanism") +set(VIEWER_SYMBOL_FILE "" CACHE STRING "Name of tarball into which to place symbol files") +set(BUGSPLAT_DB "" CACHE STRING "BugSplat database name, if BugSplat crash reporting is desired")  if(LIBS_CLOSED_DIR)    file(TO_CMAKE_PATH "${LIBS_CLOSED_DIR}" LIBS_CLOSED_DIR) @@ -209,7 +211,6 @@ set(SIGNING_IDENTITY "" CACHE STRING "Specifies the signing identity to use, if  set(VERSION_BUILD "0" CACHE STRING "Revision number passed in from the outside")  set(USESYSTEMLIBS OFF CACHE BOOL "Use libraries from your system rather than Linden-supplied prebuilt libraries.") -set(UNATTENDED OFF CACHE BOOL "Should be set to ON for building with VC Express editions.")  set(USE_PRECOMPILED_HEADERS ON CACHE BOOL "Enable use of precompiled header directives where supported.") diff --git a/indra/cmake/bugsplat.cmake b/indra/cmake/bugsplat.cmake new file mode 100644 index 0000000000..59644b73ce --- /dev/null +++ b/indra/cmake/bugsplat.cmake @@ -0,0 +1,25 @@ +# BugSplat is engaged by setting BUGSPLAT_DB to the target BugSplat database +# name. +if (BUGSPLAT_DB) +  if (USESYSTEMLIBS) +    message(STATUS "Looking for system BugSplat") +    set(BUGSPLAT_FIND_QUIETLY ON) +    set(BUGSPLAT_FIND_REQUIRED ON) +    include(FindBUGSPLAT) +  else (USESYSTEMLIBS) +    message(STATUS "Engaging autobuild BugSplat") +    include(Prebuilt) +    use_prebuilt_binary(bugsplat) +    if (WINDOWS) +      set(BUGSPLAT_LIBRARIES  +        ${ARCH_PREBUILT_DIRS_RELEASE}/bugsplat.lib +        ) +    elseif (DARWIN) +      find_library(BUGSPLAT_LIBRARIES BugsplatMac +        PATHS "${ARCH_PREBUILT_DIRS_RELEASE}") +    else (WINDOWS) + +    endif (WINDOWS) +    set(BUGSPLAT_INCLUDE_DIR ${LIBS_PREBUILT_DIR}/include/bugsplat) +  endif (USESYSTEMLIBS) +endif (BUGSPLAT_DB) diff --git a/indra/integration_tests/llimage_libtest/CMakeLists.txt b/indra/integration_tests/llimage_libtest/CMakeLists.txt index 13cf1f7bde..d9353f904c 100644 --- a/indra/integration_tests/llimage_libtest/CMakeLists.txt +++ b/indra/integration_tests/llimage_libtest/CMakeLists.txt @@ -40,7 +40,7 @@ add_executable(llimage_libtest      WIN32      MACOSX_BUNDLE      ${llimage_libtest_SOURCE_FILES} -) +    )  set_target_properties(llimage_libtest      PROPERTIES diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py index 598261e3d7..2e6cf53912 100755 --- a/indra/lib/python/indra/util/llmanifest.py +++ b/indra/lib/python/indra/util/llmanifest.py @@ -33,13 +33,14 @@ import filecmp  import fnmatch  import getopt  import glob +import itertools +import operator  import os  import re  import shutil +import subprocess  import sys  import tarfile -import errno -import subprocess  class ManifestError(RuntimeError):      """Use an exception more specific than generic Python RuntimeError""" @@ -49,7 +50,9 @@ class ManifestError(RuntimeError):  class MissingError(ManifestError):      """You specified a file that doesn't exist""" -    pass +    def __init__(self, msg): +        self.msg = msg +        super(MissingError, self).__init__(self.msg)  def path_ancestors(path):      drive, path = os.path.splitdrive(os.path.normpath(path)) @@ -90,7 +93,7 @@ DEFAULT_SRCTREE = os.path.dirname(sys.argv[0])  CHANNEL_VENDOR_BASE = 'Second Life'  RELEASE_CHANNEL = CHANNEL_VENDOR_BASE + ' Release' -ARGUMENTS=[ +BASE_ARGUMENTS=[      dict(name='actions',           description="""This argument specifies the actions that are to be taken when the          script is run.  The meaningful actions are currently: @@ -108,8 +111,19 @@ ARGUMENTS=[          Example use: %(name)s --arch=i686          On Linux this would try to use Linux_i686Manifest.""",           default=""), +    dict(name='artwork', description='Artwork directory.', default=DEFAULT_SRCTREE),      dict(name='build', description='Build directory.', default=DEFAULT_SRCTREE),      dict(name='buildtype', description='Build type (i.e. Debug, Release, RelWithDebInfo).', default=None), +    dict(name='bundleid', +         description="""The Mac OS X Bundle identifier.""", +         default="com.secondlife.indra.viewer"), +    dict(name='channel', +         description="""The channel to use for updates, packaging, settings name, etc.""", +         default='CHANNEL UNSET'), +    dict(name='channel_suffix', +         description="""Addition to the channel for packaging and channel value, +         but not application name (used internally)""", +         default=None),      dict(name='configuration',           description="""The build configuration used.""",           default="Release"), @@ -117,12 +131,6 @@ ARGUMENTS=[      dict(name='grid',           description="""Which grid the client will try to connect to.""",           default=None), -    dict(name='channel', -         description="""The channel to use for updates, packaging, settings name, etc.""", -         default='CHANNEL UNSET'), -    dict(name='channel_suffix', -         description="""Addition to the channel for packaging and channel value, but not application name (used internally)""", -         default=None),      dict(name='installer_name',           description=""" The name of the file that the installer should be          packaged up into. Only used on Linux at the moment.""", @@ -134,10 +142,14 @@ ARGUMENTS=[           description="""The current platform, to be used for looking up which          manifest class to run.""",           default=get_default_platform), +    dict(name='signature', +         description="""This specifies an identity to sign the viewer with, if any. +        If no value is supplied, the default signature will be used, if any. Currently +        only used on Mac OS X.""", +         default=None),      dict(name='source',           description='Source directory.',           default=DEFAULT_SRCTREE), -    dict(name='artwork', description='Artwork directory.', default=DEFAULT_SRCTREE),      dict(name='touch',           description="""File to touch when action is finished. Touch file will          contain the name of the final package in a form suitable @@ -145,23 +157,15 @@ ARGUMENTS=[           default=None),      dict(name='versionfile',           description="""The name of a file containing the full version number."""), -    dict(name='bundleid', -         description="""The Mac OS X Bundle identifier.""", -         default="com.secondlife.indra.viewer"), -    dict(name='signature', -         description="""This specifies an identity to sign the viewer with, if any. -        If no value is supplied, the default signature will be used, if any. Currently -        only used on Mac OS X.""", -         default=None)      ] -def usage(srctree=""): +def usage(arguments, srctree=""):      nd = {'name':sys.argv[0]}      print """Usage:      %(name)s [options] [destdir]      Options:      """ % nd -    for arg in ARGUMENTS: +    for arg in arguments:          default = arg['default']          if hasattr(default, '__call__'):              default = "(computed value) \"" + str(default(srctree)) + '"' @@ -172,11 +176,15 @@ def usage(srctree=""):              default,              arg['description'] % nd) -def main(): -##  import itertools +def main(extra=[]):  ##  print ' '.join((("'%s'" % item) if ' ' in item else item)  ##                 for item in itertools.chain([sys.executable], sys.argv)) -    option_names = [arg['name'] + '=' for arg in ARGUMENTS] +    # Supplement our default command-line switches with any desired by +    # application-specific caller. +    arguments = list(itertools.chain(BASE_ARGUMENTS, extra)) +    # Alphabetize them by option name in case we display usage. +    arguments.sort(key=operator.itemgetter('name')) +    option_names = [arg['name'] + '=' for arg in arguments]      option_names.append('help')      options, remainder = getopt.getopt(sys.argv[1:], "", option_names) @@ -199,11 +207,11 @@ def main():      # early out for help      if 'help' in args:          # *TODO: it is a huge hack to pass around the srctree like this -        usage(args['source']) +        usage(arguments, srctree=args['source'])          return      # defaults -    for arg in ARGUMENTS: +    for arg in arguments:          if arg['name'] not in args:              default = arg['default']              if hasattr(default, '__call__'): @@ -232,104 +240,68 @@ def main():          print "Option:", opt, "=", args[opt]      # pass in sourceid as an argument now instead of an environment variable -    try: -        args['sourceid'] = os.environ["sourceid"] -    except KeyError: -        args['sourceid'] = "" +    args['sourceid'] = os.environ.get("sourceid", "")      # Build base package.      touch = args.get('touch')      if touch: -        print 'Creating base package' -    args['package_id'] = "" # base package has no package ID +        print '================ Creating base package' +    else: +        print '================ Starting base copy'      wm = LLManifest.for_platform(args['platform'], args.get('arch'))(args)      wm.do(*args['actions'])      # Store package file for later if making touched file.      base_package_file = ""      if touch: -        print 'Created base package ', wm.package_file +        print '================ Created base package ', wm.package_file          base_package_file = "" + wm.package_file +    else: +        print '================ Finished base copy'      # handle multiple packages if set -    try: -        additional_packages = os.environ["additional_packages"] -    except KeyError: -        additional_packages = "" +    # ''.split() produces empty list +    additional_packages = os.environ.get("additional_packages", "").split()      if additional_packages:          # Determine destination prefix / suffix for additional packages. -        base_dest_postfix = args['dest'] -        base_dest_prefix = "" -        base_dest_parts = args['dest'].split(os.sep) -        if len(base_dest_parts) > 1: -            base_dest_postfix = base_dest_parts[len(base_dest_parts) - 1] -            base_dest_prefix = base_dest_parts[0] -            i = 1 -            while i < len(base_dest_parts) - 1: -                base_dest_prefix = base_dest_prefix + os.sep + base_dest_parts[i] -                i = i + 1 +        base_dest_parts = list(os.path.split(args['dest'])) +        base_dest_parts.insert(-1, "{}") +        base_dest_template = os.path.join(*base_dest_parts)          # Determine touched prefix / suffix for additional packages. -        base_touch_postfix = "" -        base_touch_prefix = ""          if touch: -            base_touch_postfix = touch -            base_touch_parts = touch.split('/') +            base_touch_parts = list(os.path.split(touch)) +            # Because of the special insert() logic below, we don't just want +            # [dirpath, basename]; we want [dirpath, directory, basename]. +            # Further split the dirpath and replace it in the list. +            base_touch_parts[0:1] = os.path.split(base_touch_parts[0])              if "arwin" in args['platform']: -                if len(base_touch_parts) > 1: -                    base_touch_postfix = base_touch_parts[len(base_touch_parts) - 1] -                    base_touch_prefix = base_touch_parts[0] -                    i = 1 -                    while i < len(base_touch_parts) - 1: -                        base_touch_prefix = base_touch_prefix + '/' + base_touch_parts[i] -                        i = i + 1 +                base_touch_parts.insert(-1, "{}")              else: -                if len(base_touch_parts) > 2: -                    base_touch_postfix = base_touch_parts[len(base_touch_parts) - 2] + '/' + base_touch_parts[len(base_touch_parts) - 1] -                    base_touch_prefix = base_touch_parts[0] -                    i = 1 -                    while i < len(base_touch_parts) - 2: -                        base_touch_prefix = base_touch_prefix + '/' + base_touch_parts[i] -                        i = i + 1 -        # Store base channel name. -        base_channel_name = args['channel'] -        # Build each additional package. -        package_id_list = additional_packages.split(" ") -        args['channel'] = base_channel_name -        for package_id in package_id_list: -            try: -                if package_id + "_viewer_channel_suffix" in os.environ: -                    args['channel_suffix'] = os.environ[package_id + "_viewer_channel_suffix"] -                else: -                    args['channel_suffix'] = None -                if package_id + "_sourceid" in os.environ: -                    args['sourceid'] = os.environ[package_id + "_sourceid"] -                else: -                    args['sourceid'] = None -                args['dest'] = base_dest_prefix + os.sep + package_id + os.sep + base_dest_postfix -            except KeyError: -                sys.stderr.write("Failed to create package for package_id: %s" % package_id) -                sys.stderr.flush() -                continue +                base_touch_parts.insert(-2, "{}") +            base_touch_template = os.path.join(*base_touch_parts) +        for package_id in additional_packages: +            args['channel_suffix'] = os.environ.get(package_id + "_viewer_channel_suffix") +            args['sourceid']       = os.environ.get(package_id + "_sourceid") +            args['dest'] = base_dest_template.format(package_id)              if touch: -                print 'Creating additional package for "', package_id, '" in ', args['dest'] +                print '================ Creating additional package for "', package_id, '" in ', args['dest'] +            else: +                print '================ Starting additional copy for "', package_id, '" in ', args['dest']              try:                  wm = LLManifest.for_platform(args['platform'], args.get('arch'))(args)                  wm.do(*args['actions'])              except Exception as err:                  sys.exit(str(err))              if touch: -                print 'Created additional package ', wm.package_file, ' for ', package_id -                faketouch = base_touch_prefix + '/' + package_id + '/' + base_touch_postfix -                fp = open(faketouch, 'w') -                fp.write('set package_file=%s\n' % wm.package_file) -                fp.close() -     +                print '================ Created additional package ', wm.package_file, ' for ', package_id +                with open(base_touch_template.format(package_id), 'w') as fp: +                    fp.write('set package_file=%s\n' % wm.package_file) +            else: +                print '================ Finished additional copy "', package_id, '" in ', args['dest']      # Write out the package file in this format, so that it can easily be called      # and used in a .bat file - yeah, it sucks, but this is the simplest... -    touch = args.get('touch')      if touch: -        fp = open(touch, 'w') -        fp.write('set package_file=%s\n' % base_package_file) -        fp.close() +        with open(touch, 'w') as fp: +            fp.write('set package_file=%s\n' % base_package_file)          print 'touched', touch      return 0 @@ -375,7 +347,7 @@ class LLManifest(object):          in the file list by path()."""          self.excludes.append(glob) -    def prefix(self, src='', build=None, dst=None): +    def prefix(self, src='', build='', dst='', src_dst=None):          """          Usage: @@ -385,8 +357,21 @@ class LLManifest(object):          For the duration of the 'with' block, pushes a prefix onto the stack.          Within that block, all relevant method calls (esp. to path()) will          prefix paths with the entire prefix stack. Source and destination -        prefixes can be different, though if only one is provided they are -        both equal. To specify a no-op, use an empty string, not None. +        prefixes are independent; if omitted (or passed as the empty string), +        the prefix has no effect. Thus: + +        with self.prefix(src='foo'): +            # no effect on dst + +        with self.prefix(dst='bar'): +            # no effect on src + +        If you want to set both at once, use src_dst: + +        with self.prefix(src_dst='subdir'): +            # same as self.prefix(src='subdir', dst='subdir') +            # Passing src_dst makes any src or dst argument in the same +            # parameter list irrelevant.          Also supports the older (pre-Python-2.5) syntax: @@ -400,34 +385,42 @@ class LLManifest(object):          returned True specifically so that the caller could indent the          relevant block of code with 'if', just for aesthetic purposes.          """ -        if dst is None: -            dst = src -        if build is None: -            build = src +        if src_dst is not None: +            src = src_dst +            dst = src_dst          self.src_prefix.append(src)          self.artwork_prefix.append(src)          self.build_prefix.append(build)          self.dst_prefix.append(dst) +##      self.display_stacks() +          # The above code is unchanged from the original implementation. What's          # new is the return value. We're going to return an instance of          # PrefixManager that binds this LLManifest instance and Does The Right          # Thing on exit.          return self.PrefixManager(self) +    def display_stacks(self): +        width = 1 + max(len(stack) for stack in self.PrefixManager.stacks) +        for stack in self.PrefixManager.stacks: +            print "{} {}".format((stack + ':').ljust(width), +                                 os.path.join(*getattr(self, stack))) +      class PrefixManager(object): +        # stack attributes we manage in this LLManifest (sub)class +        # instance +        stacks = ("src_prefix", "artwork_prefix", "build_prefix", "dst_prefix") +          def __init__(self, manifest):              self.manifest = manifest -            # stack attributes we manage in this LLManifest (sub)class -            # instance -            stacks = ("src_prefix", "artwork_prefix", "build_prefix", "dst_prefix")              # If the caller wrote:              # with self.prefix(...):              # as intended, then bind the state of each prefix stack as it was              # just BEFORE the call to prefix(). Since prefix() appended an              # entry to each prefix stack, capture len()-1.              self.prevlen = { stack: len(getattr(self.manifest, stack)) - 1 -                             for stack in stacks } +                             for stack in self.stacks }          def __nonzero__(self):              # If the caller wrote: @@ -460,6 +453,8 @@ class LLManifest(object):                  # truncate that list back to 'prevlen'                  del getattr(self.manifest, stack)[prevlen:] +##          self.manifest.display_stacks() +      def end_prefix(self, descr=None):          """Pops a prefix off the stack.  If given an argument, checks          the argument against the top of the stack.  If the argument @@ -505,6 +500,19 @@ class LLManifest(object):          relative to the destination directory."""          return os.path.join(self.get_dst_prefix(), relpath) +    def _relative_dst_path(self, dstpath): +        """ +        Returns the path to a file or directory relative to the destination directory. +        This should only be used for generating diagnostic output in the path method. +        """ +        dest_root=self.dst_prefix[0] +        if dstpath.startswith(dest_root+os.path.sep): +            return dstpath[len(dest_root)+1:] +        elif dstpath.startswith(dest_root): +            return dstpath[len(dest_root):] +        else: +            return dstpath +      def ensure_src_dir(self, reldir):          """Construct the path for a directory relative to the          source path, and ensures that it exists.  Returns the @@ -607,9 +615,16 @@ class LLManifest(object):              # *TODO is this gonna be useful?              print "Cleaning up " + c +    def process_either(self, src, dst): +        # If it's a real directory, recurse through it -- +        # but not a symlink! Handle those like files. +        if os.path.isdir(src) and not os.path.islink(src): +            return self.process_directory(src, dst) +        else: +            return self.process_file(src, dst) +      def process_file(self, src, dst):          if self.includes(src, dst): -#            print src, "=>", dst              for action in self.actions:                  methodname = action + "_action"                  method = getattr(self, methodname, None) @@ -634,10 +649,7 @@ class LLManifest(object):          for name in names:              srcname = os.path.join(src, name)              dstname = os.path.join(dst, name) -            if os.path.isdir(srcname): -                count += self.process_directory(srcname, dstname) -            else: -                count += self.process_file(srcname, dstname) +            count += self.process_either(srcname, dstname)          return count      def includes(self, src, dst): @@ -677,7 +689,11 @@ class LLManifest(object):          # Don't recopy file if it's up-to-date.          # If we seem to be not not overwriting files that have been          # updated, set the last arg to False, but it will take longer. +##      reldst = (dst[len(self.dst_prefix[0]):] +##                if dst.startswith(self.dst_prefix[0]) +##                else dst).lstrip(r'\/')          if os.path.exists(dst) and filecmp.cmp(src, dst, True): +##          print "{} (skipping, {} exists)".format(src, reldst)              return          # only copy if it's not excluded          if self.includes(src, dst): @@ -687,6 +703,7 @@ class LLManifest(object):                  if err.errno != errno.ENOENT:                      raise +##          print "{} => {}".format(src, reldst)              shutil.copy2(src, dst)      def ccopytree(self, src, dst): @@ -785,13 +802,13 @@ class LLManifest(object):          return self.path(os.path.join(path, file), file)      def path(self, src, dst=None): -        sys.stdout.write("Processing %s => %s ... " % (src, dst))          sys.stdout.flush()          if src == None:              raise ManifestError("No source file, dst is " + dst)          if dst == None:              dst = src          dst = os.path.join(self.get_dst_prefix(), dst) +        sys.stdout.write("Processing %s => %s ... " % (src, self._relative_dst_path(dst)))          def try_path(src):              # expand globs @@ -804,29 +821,21 @@ class LLManifest(object):                  # if we're specifying a single path (not a glob),                  # we should error out if it doesn't exist                  self.check_file_exists(src) -                # if it's a directory, recurse through it -                if os.path.isdir(src): -                    count += self.process_directory(src, dst) -                else: -                    count += self.process_file(src, dst) +                count += self.process_either(src, dst)              return count -        for pfx in self.get_src_prefix(), self.get_artwork_prefix(), self.get_build_prefix(): +        try_prefixes = [self.get_src_prefix(), self.get_artwork_prefix(), self.get_build_prefix()] +        tried=[] +        count=0 +        while not count and try_prefixes:  +            pfx = try_prefixes.pop(0)              try:                  count = try_path(os.path.join(pfx, src))              except MissingError: -                # If src isn't a wildcard, and if that file doesn't exist in -                # this pfx, try next pfx. -                count = 0 -                continue - -            # Here try_path() didn't raise MissingError. Did it process any files? -            if count: -                break -            # Even though try_path() didn't raise MissingError, it returned 0 -            # files. src is probably a wildcard meant for some other pfx. Loop -            # back to try the next. - +                tried.append(pfx) +                if not try_prefixes: +                    # no more prefixes left to try +                    print "unable to find '%s'; looked in:\n  %s" % (src, '\n  '.join(tried))          print "%d files" % count          # Let caller check whether we processed as many files as expected. In diff --git a/indra/linux_crash_logger/CMakeLists.txt b/indra/linux_crash_logger/CMakeLists.txt index 029096df37..315aed8d11 100644 --- a/indra/linux_crash_logger/CMakeLists.txt +++ b/indra/linux_crash_logger/CMakeLists.txt @@ -78,4 +78,4 @@ target_link_libraries(linux-crash-logger      )  add_custom_target(linux-crash-logger-target ALL -                  DEPENDS linux-crash-logger) +    DEPENDS linux-crash-logger) diff --git a/indra/llappearance/llavatarappearance.cpp b/indra/llappearance/llavatarappearance.cpp index 92217c60ff..38cda2e2f1 100644 --- a/indra/llappearance/llavatarappearance.cpp +++ b/indra/llappearance/llavatarappearance.cpp @@ -1348,9 +1348,9 @@ LLVector3 LLAvatarAppearance::getVolumePos(S32 joint_index, LLVector3& volume_of  //-----------------------------------------------------------------------------  // findCollisionVolume()  //----------------------------------------------------------------------------- -LLJoint* LLAvatarAppearance::findCollisionVolume(U32 volume_id) +LLJoint* LLAvatarAppearance::findCollisionVolume(S32 volume_id)  { -	if ((S32)volume_id > mNumCollisionVolumes) +	if ((volume_id < 0) || (volume_id >= mNumCollisionVolumes))  	{  		return NULL;  	} diff --git a/indra/llappearance/llavatarappearance.h b/indra/llappearance/llavatarappearance.h index 7815c1844b..6a4dbf3726 100644 --- a/indra/llappearance/llavatarappearance.h +++ b/indra/llappearance/llavatarappearance.h @@ -93,7 +93,7 @@ public:  	/*virtual*/ const char*		getAnimationPrefix() { return "avatar"; }  	/*virtual*/ LLVector3		getVolumePos(S32 joint_index, LLVector3& volume_offset); -	/*virtual*/ LLJoint*		findCollisionVolume(U32 volume_id); +	/*virtual*/ LLJoint*		findCollisionVolume(S32 volume_id);  	/*virtual*/ S32				getCollisionVolumeID(std::string &name);  	/*virtual*/ LLPolyMesh*		getHeadMesh();  	/*virtual*/ LLPolyMesh*		getUpperBodyMesh(); diff --git a/indra/llappearance/lllocaltextureobject.cpp b/indra/llappearance/lllocaltextureobject.cpp index f49cf21512..3f564ec3de 100644 --- a/indra/llappearance/lllocaltextureobject.cpp +++ b/indra/llappearance/lllocaltextureobject.cpp @@ -210,4 +210,3 @@ void LLLocalTextureObject::setBakedReady(BOOL ready)  {  	mIsBakedReady = ready;  } - diff --git a/indra/llcharacter/llcharacter.h b/indra/llcharacter/llcharacter.h index 1a3e307663..2fac5f53a6 100644 --- a/indra/llcharacter/llcharacter.h +++ b/indra/llcharacter/llcharacter.h @@ -177,7 +177,7 @@ public:  	virtual LLVector3 getVolumePos(S32 joint_index, LLVector3& volume_offset) { return LLVector3::zero; } -	virtual LLJoint* findCollisionVolume(U32 volume_id) { return NULL; } +	virtual LLJoint* findCollisionVolume(S32 volume_id) { return NULL; }  	virtual S32 getCollisionVolumeID(std::string &name) { return -1; } diff --git a/indra/llcharacter/llkeyframemotion.cpp b/indra/llcharacter/llkeyframemotion.cpp index 330d812985..5d323ed5d6 100644 --- a/indra/llcharacter/llkeyframemotion.cpp +++ b/indra/llcharacter/llkeyframemotion.cpp @@ -1772,6 +1772,13 @@ BOOL LLKeyframeMotion::deserialize(LLDataPacker& dp, const LLUUID& asset_id)  			bin_data[BIN_DATA_LENGTH] = 0; // Ensure null termination  			str = (char*)bin_data;  			constraintp->mSourceConstraintVolume = mCharacter->getCollisionVolumeID(str); +			if (constraintp->mSourceConstraintVolume == -1) +			{ +				LL_WARNS() << "not a valid source constraint volume " << str +						   << " for animation " << asset_id << LL_ENDL; +				delete constraintp; +				return FALSE; +			}  			if (!dp.unpackVector3(constraintp->mSourceConstraintOffset, "source_offset"))  			{ @@ -1808,6 +1815,13 @@ BOOL LLKeyframeMotion::deserialize(LLDataPacker& dp, const LLUUID& asset_id)  			{  				constraintp->mConstraintTargetType = CONSTRAINT_TARGET_TYPE_BODY;  				constraintp->mTargetConstraintVolume = mCharacter->getCollisionVolumeID(str); +				if (constraintp->mTargetConstraintVolume == -1) +				{ +					LL_WARNS() << "not a valid target constraint volume " << str +							   << " for animation " << asset_id << LL_ENDL; +					delete constraintp; +					return FALSE; +				}  			}  			if (!dp.unpackVector3(constraintp->mTargetConstraintOffset, "target_offset")) diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt index d9eb13d65a..42ad56f1b0 100644 --- a/indra/llcommon/CMakeLists.txt +++ b/indra/llcommon/CMakeLists.txt @@ -255,6 +255,11 @@ set(llcommon_HEADER_FILES  set_source_files_properties(${llcommon_HEADER_FILES}                              PROPERTIES HEADER_FILE_ONLY TRUE) +if (BUGSPLAT_DB) +  set_source_files_properties(llapp.cpp +    PROPERTIES COMPILE_DEFINITIONS "LL_BUGSPLAT") +endif (BUGSPLAT_DB) +  list(APPEND llcommon_SOURCE_FILES ${llcommon_HEADER_FILES})  if(LLCOMMON_LINK_SHARED) diff --git a/indra/llcommon/llapp.cpp b/indra/llcommon/llapp.cpp index 6cc9e804d4..421af3006e 100644 --- a/indra/llcommon/llapp.cpp +++ b/indra/llcommon/llapp.cpp @@ -392,7 +392,7 @@ void LLApp::setupErrorHandling(bool second_instance)  #if LL_WINDOWS -#if LL_SEND_CRASH_REPORTS +#if LL_SEND_CRASH_REPORTS && ! defined(LL_BUGSPLAT)  	EnableCrashingOnCrashes();  	// This sets a callback to handle w32 signals to the console window. @@ -454,8 +454,15 @@ void LLApp::setupErrorHandling(bool second_instance)  			mExceptionHandler->set_handle_debug_exceptions(true);  		}  	} -#endif -#else +#endif // LL_SEND_CRASH_REPORTS && ! defined(LL_BUGSPLAT) +#else  // ! LL_WINDOWS + +#if defined(LL_BUGSPLAT) +	// Don't install our own signal handlers -- BugSplat needs to hook them, +	// or it's completely ineffectual. +	bool installHandler = false; + +#else // ! LL_BUGSPLAT  	//  	// Start up signal handling.  	// @@ -463,9 +470,11 @@ void LLApp::setupErrorHandling(bool second_instance)  	// thread, asynchronous signals can be delivered to any thread (in theory)  	//  	setup_signals(); -	 +  	// Add google breakpad exception handler configured for Darwin/Linux.  	bool installHandler = true; +#endif // ! LL_BUGSPLAT +  #if LL_DARWIN  	// For the special case of Darwin, we do not want to install the handler if  	// the process is being debugged as the app will exit with value ABRT (6) if @@ -498,7 +507,7 @@ void LLApp::setupErrorHandling(bool second_instance)  		// installing the handler.  		installHandler = true;  	} -	#endif +	#endif // ! LL_RELEASE_FOR_DOWNLOAD  	if(installHandler && (mExceptionHandler == 0))  	{ @@ -514,9 +523,9 @@ void LLApp::setupErrorHandling(bool second_instance)  		google_breakpad::MinidumpDescriptor desc(mDumpPath);  	    mExceptionHandler = new google_breakpad::ExceptionHandler(desc, NULL, unix_minidump_callback, NULL, true, -1);  	} -#endif +#endif // LL_LINUX -#endif +#endif // ! LL_WINDOWS  	startErrorThread();  } diff --git a/indra/llcommon/llcoros.h b/indra/llcommon/llcoros.h index 8fb27af6a4..c551413811 100644 --- a/indra/llcommon/llcoros.h +++ b/indra/llcommon/llcoros.h @@ -170,6 +170,26 @@ public:      static bool get_consuming();      /** +     * RAII control of the consuming flag +     */ +    class OverrideConsuming +    { +    public: +        OverrideConsuming(bool consuming): +            mPrevConsuming(get_consuming()) +        { +            set_consuming(consuming); +        } +        ~OverrideConsuming() +        { +            set_consuming(mPrevConsuming); +        } + +    private: +        bool mPrevConsuming; +    }; + +    /**       * Please do NOT directly use boost::dcoroutines::future! It is essential       * to maintain the "current" coroutine at every context switch. This       * Future wraps the essential boost::dcoroutines::future functionality diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp index 40eb7d9bac..7cfd1409b1 100644 --- a/indra/llcommon/llerror.cpp +++ b/indra/llcommon/llerror.cpp @@ -132,8 +132,6 @@ namespace {                      mFile.sync_with_stdio(false);                  }              } -			mWantsTime = true; -            mWantsTags = true;  		}  		~RecordToFile() @@ -175,7 +173,7 @@ namespace {  	public:  		RecordToStderr(bool timestamp) : mUseANSI(ANSI_PROBE)   		{ -			mWantsTime = timestamp; +            this->showMultiline(true);  		}          virtual bool enabled() override @@ -241,7 +239,13 @@ namespace {  	class RecordToFixedBuffer : public LLError::Recorder  	{  	public: -		RecordToFixedBuffer(LLLineBuffer* buffer) : mBuffer(buffer) { } +		RecordToFixedBuffer(LLLineBuffer* buffer) +            : mBuffer(buffer) +            { +                this->showMultiline(true); +                this->showTags(false); +                this->showLocation(false); +            }          virtual bool enabled() override          { @@ -263,7 +267,11 @@ namespace {  	{  	public:  		RecordToWinDebug() -		{} +		{ +            this->showMultiline(true); +            this->showTags(false); +            this->showLocation(false); +        }          virtual bool enabled() override          { @@ -412,6 +420,7 @@ namespace  	public:  		std::ostringstream messageStream;  		bool messageStreamInUse; +		std::string mFatalMessage;  		void addCallSite(LLError::CallSite&);  		void invalidateCallSites(); @@ -454,8 +463,6 @@ namespace LLError  	public:  		virtual ~SettingsConfig(); -		bool                                mPrintLocation; -  		LLError::ELevel                     mDefaultLevel;          bool 								mLogAlwaysFlush; @@ -500,7 +507,6 @@ namespace LLError  	SettingsConfig::SettingsConfig()  		: LLRefCount(), -		mPrintLocation(false),  		mDefaultLevel(LLError::LEVEL_DEBUG),  		mLogAlwaysFlush(true),  		mEnabledLogTypesMask(255), @@ -706,23 +712,22 @@ namespace LLError  		commonInit(user_dir, app_dir, log_to_stderr);  	} -	void setPrintLocation(bool print) +	void setFatalFunction(const FatalFunction& f)  	{  		SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig(); -		s->mPrintLocation = print; +		s->mCrashFunction = f;  	} -	void setFatalFunction(const FatalFunction& f) +	FatalFunction getFatalFunction()  	{  		SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig(); -		s->mCrashFunction = f; +		return s->mCrashFunction;  	} -    FatalFunction getFatalFunction() -    { -		SettingsConfigPtr s = Settings::getInstance()->getSettingsConfig(); -        return s->mCrashFunction; -    } +	std::string getFatalMessage() +	{ +		return Globals::getInstance()->mFatalMessage; +	}  	void setTimeFunction(TimeFunction f)  	{ @@ -845,7 +850,6 @@ namespace LLError  		s->mTagLevelMap.clear();  		s->mUniqueLogMessages.clear(); -		setPrintLocation(config["print-location"]);  		setDefaultLevel(decodeLevel(config["default-level"]));          if (config.has("log-always-flush"))          { @@ -876,11 +880,12 @@ namespace LLError  namespace LLError  {  	Recorder::Recorder() -	:	mWantsTime(false), -		mWantsTags(false), -		mWantsLevel(true), -		mWantsLocation(false), -		mWantsFunctionName(true) +    	: mWantsTime(true) +        , mWantsTags(true) +        , mWantsLevel(true) +        , mWantsLocation(true) +        , mWantsFunctionName(true) +        , mWantsMultiline(false)  	{  	} @@ -917,6 +922,42 @@ namespace LLError  		return mWantsFunctionName;  	} +	// virtual  +	bool Recorder::wantsMultiline()  +	{  +		return mWantsMultiline; +	} + +    void Recorder::showTime(bool show) +    { +        mWantsTime = show; +    } +     +    void Recorder::showTags(bool show) +    { +        mWantsTags = show; +    } + +    void Recorder::showLevel(bool show) +    { +        mWantsLevel = show; +    } + +    void Recorder::showLocation(bool show) +    { +        mWantsLocation = show; +    } + +    void Recorder::showFunctionName(bool show) +    { +        mWantsFunctionName = show; +    } + +    void Recorder::showMultiline(bool show) +    { +        mWantsMultiline = show; +    } +  	void addRecorder(RecorderPtr recorder)  	{  		if (!recorder) @@ -949,17 +990,15 @@ namespace LLError  		s->mFileRecorder.reset();  		s->mFileRecorderFileName.clear(); -		if (file_name.empty()) +		if (!file_name.empty())  		{ -			return; -		} -		 -		RecorderPtr recordToFile(new RecordToFile(file_name)); -		if (boost::dynamic_pointer_cast<RecordToFile>(recordToFile)->okay()) -		{ -			s->mFileRecorderFileName = file_name; -			s->mFileRecorder = recordToFile; -			addRecorder(recordToFile); +            RecorderPtr recordToFile(new RecordToFile(file_name)); +            if (boost::dynamic_pointer_cast<RecordToFile>(recordToFile)->okay()) +            { +                s->mFileRecorderFileName = file_name; +                s->mFileRecorder = recordToFile; +                addRecorder(recordToFile); +            }  		}  	} @@ -970,14 +1009,12 @@ namespace LLError  		removeRecorder(s->mFixedBufferRecorder);  		s->mFixedBufferRecorder.reset(); -		if (!fixedBuffer) +		if (fixedBuffer)  		{ -			return; -		} -		 -		RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer)); -		s->mFixedBufferRecorder = recordToFixedBuffer; -		addRecorder(recordToFixedBuffer); +            RecorderPtr recordToFixedBuffer(new RecordToFixedBuffer(fixedBuffer)); +            s->mFixedBufferRecorder = recordToFixedBuffer; +            addRecorder(recordToFixedBuffer); +        }  	}  	std::string logFileName() @@ -989,8 +1026,9 @@ namespace LLError  namespace  { -    void addEscapedMessage(std::ostream& out, const std::string& message) +    std::string escapedMessageLines(const std::string& message)      { +        std::ostringstream out;          size_t written_out = 0;          size_t all_content = message.length();          size_t escape_char_index; // always relative to start of message @@ -1026,13 +1064,16 @@ namespace              // write whatever was left              out << message.substr(written_out, std::string::npos);          } +        return out.str();      } -	void writeToRecorders(const LLError::CallSite& site, const std::string& escaped_message) +	void writeToRecorders(const LLError::CallSite& site, const std::string& message)  	{  		LLError::ELevel level = site.mLevel;  		LLError::SettingsConfigPtr s = LLError::Settings::getInstance()->getSettingsConfig(); -	 + +        std::string escaped_message; +          		for (Recorders::const_iterator i = s->mRecorders.begin();  			i != s->mRecorders.end();  			++i) @@ -1064,7 +1105,7 @@ namespace  			}              message_stream << " "; -            if (r->wantsLocation() || level == LLError::LEVEL_ERROR || s->mPrintLocation) +            if (r->wantsLocation() || level == LLError::LEVEL_ERROR)              {                  message_stream << site.mLocationString;              } @@ -1076,7 +1117,18 @@ namespace  			}              message_stream << " : "; -			message_stream << escaped_message; +            if (r->wantsMultiline()) +            { +                message_stream << message; +            } +            else +            { +                if (escaped_message.empty()) +                { +                    escaped_message = escapedMessageLines(message); +                } +                message_stream << escaped_message; +            }  			r->recordMessage(level, message_stream.str());  		} @@ -1320,10 +1372,11 @@ namespace LLError  			delete out;  		} -		std::ostringstream message_stream;  		if (site.mPrintOnce)  		{ +            std::ostringstream message_stream; +  			std::map<std::string, unsigned int>::iterator messageIter = s->mUniqueLogMessages.find(message);  			if (messageIter != s->mUniqueLogMessages.end())  			{ @@ -1343,15 +1396,19 @@ namespace LLError  				message_stream << "ONCE: ";  				s->mUniqueLogMessages[message] = 1;  			} +            message_stream << message; +            message = message_stream.str();  		} -		addEscapedMessage(message_stream, message); +		writeToRecorders(site, message); -		writeToRecorders(site, message_stream.str()); -		 -		if (site.mLevel == LEVEL_ERROR  &&  s->mCrashFunction) +		if (site.mLevel == LEVEL_ERROR)  		{ -			s->mCrashFunction(message_stream.str()); +			g->mFatalMessage = message; +			if (s->mCrashFunction) +			{ +				s->mCrashFunction(message); +			}  		}  	}  } @@ -1656,3 +1713,4 @@ bool debugLoggingEnabled(const std::string& tag)  } + diff --git a/indra/llcommon/llerrorcontrol.h b/indra/llcommon/llerrorcontrol.h index 1730f0c640..276d22fc36 100644 --- a/indra/llcommon/llerrorcontrol.h +++ b/indra/llcommon/llerrorcontrol.h @@ -106,6 +106,9 @@ namespace LLError  	LL_COMMON_API FatalFunction getFatalFunction();  		// Retrieve the previously-set FatalFunction +	LL_COMMON_API std::string getFatalMessage(); +		// Retrieve the message last passed to FatalFunction, if any +  	/// temporarily override the FatalFunction for the duration of a  	/// particular scope, e.g. for unit tests  	class LL_COMMON_API OverrideFatalFunction @@ -151,13 +154,22 @@ namespace LLError  		bool wantsLevel();  		bool wantsLocation();   		bool wantsFunctionName(); +        bool wantsMultiline(); + +		void showTime(bool show); +		void showTags(bool show); +		void showLevel(bool show); +		void showLocation(bool show);  +		void showFunctionName(bool show); +		void showMultiline(bool show);  	protected: -		bool	mWantsTime, -				mWantsTags, -				mWantsLevel, -				mWantsLocation, -				mWantsFunctionName; +		bool mWantsTime; +        bool mWantsTags; +        bool mWantsLevel; +        bool mWantsLocation; +        bool mWantsFunctionName; +        bool mWantsMultiline;  	};  	typedef boost::shared_ptr<Recorder> RecorderPtr; diff --git a/indra/llcommon/llevents.cpp b/indra/llcommon/llevents.cpp index dce97b5411..eedd8c92b5 100644 --- a/indra/llcommon/llevents.cpp +++ b/indra/llcommon/llevents.cpp @@ -545,10 +545,8 @@ bool LLEventStream::post(const LLSD& event)   *****************************************************************************/  bool LLEventMailDrop::post(const LLSD& event)  { -    bool posted = false; -     -    if (!mSignal->empty()) -        posted = LLEventStream::post(event); +    // forward the call to our base class +    bool posted = LLEventStream::post(event);      if (!posted)      {   // if the event was not handled we will save it for later so that it can  @@ -564,16 +562,25 @@ LLBoundListener LLEventMailDrop::listen_impl(const std::string& name,                                      const NameList& after,                                      const NameList& before)  { -    if (!mEventHistory.empty()) +    // Before actually connecting this listener for subsequent post() calls, +    // first feed each of the saved events, in order, to the new listener. +    // Remove any that this listener consumes -- Effective STL, Item 9. +    for (auto hi(mEventHistory.begin()), hend(mEventHistory.end()); hi != hend; )      { -        if (listener(mEventHistory.front())) +        if (listener(*hi))          { -            mEventHistory.pop_front(); +            // new listener consumed this event, erase it +            hi = mEventHistory.erase(hi); +        } +        else +        { +            // listener did not consume this event, just move along +            ++hi;          }      } +    // let base class perform the actual connection      return LLEventStream::listen_impl(name, listener, after, before); -  } diff --git a/indra/llcommon/llevents.h b/indra/llcommon/llevents.h index 1d51c660ed..5d60c63810 100644 --- a/indra/llcommon/llevents.h +++ b/indra/llcommon/llevents.h @@ -650,15 +650,21 @@ public:   *   LLEventMailDrop   *****************************************************************************/  /** - * LLEventMailDrop is a specialization of LLEventStream. Events are posted normally,  - * however if no listeners return that they have handled the event it is placed in  - * a queue. Subsequent attaching listeners will receive stored events from the queue  - * until a listener indicates that the event has been handled.  In order to receive  - * multiple events from a mail drop the listener must disconnect and reconnect. + * LLEventMailDrop is a specialization of LLEventStream. Events are posted + * normally, however if no listener returns that it has handled the event + * (returns true), it is placed in a queue. Subsequent attaching listeners + * will receive stored events from the queue until some listener indicates + * that the event has been handled. + * + * LLEventMailDrop completely decouples the timing of post() calls from + * listen() calls: every event posted to an LLEventMailDrop is eventually seen + * by all listeners, until some listener consumes it. The caveat is that each + * event *must* eventually reach a listener that will consume it, else the + * queue will grow to arbitrary length.   *    * @NOTE: When using an LLEventMailDrop (or LLEventQueue) with a LLEventTimeout or - * LLEventFilter attaching the filter downstream using Timeout's constructor will - * cause the MailDrop to discharge any of it's stored events. The timeout should  + * LLEventFilter attaching the filter downstream, using Timeout's constructor will + * cause the MailDrop to discharge any of its stored events. The timeout should    * instead be connected upstream using its listen() method.     * See llcoro::suspendUntilEventOnWithTimeout() for an example.   */ diff --git a/indra/llcommon/llfile.cpp b/indra/llcommon/llfile.cpp index fc203f78e1..8355b1e797 100644 --- a/indra/llcommon/llfile.cpp +++ b/indra/llcommon/llfile.cpp @@ -30,6 +30,7 @@  #if LL_WINDOWS  #include "llwin32headerslean.h"  #include <stdlib.h>                 // Windows errno +#include <vector>  #else  #include <errno.h>  #endif @@ -134,8 +135,10 @@ int warnif(const std::string& desc, const std::string& filename, int rc, int acc  		{  			// Only do any of this stuff (before LL_ENDL) if it will be logged.  			LL_DEBUGS("LLFile") << empty; -			const char* TEMP = getenv("TEMP"); -			if (! TEMP) +			// would be nice to use LLDir for this, but dependency goes the +			// wrong way +			const char* TEMP = LLFile::tmpdir(); +			if (! (TEMP && *TEMP))  			{  				LL_CONT << "No $TEMP, not running 'handle'";  			} @@ -341,17 +344,13 @@ const char *LLFile::tmpdir()  #if LL_WINDOWS  		sep = '\\'; -		DWORD len = GetTempPathW(0, L""); -		llutf16string utf16path; -		utf16path.resize(len + 1); -		len = GetTempPathW(static_cast<DWORD>(utf16path.size()), &utf16path[0]); -		utf8path = utf16str_to_utf8str(utf16path); +		std::vector<wchar_t> utf16path(MAX_PATH + 1); +		GetTempPathW(utf16path.size(), &utf16path[0]); +		utf8path = ll_convert_wide_to_string(&utf16path[0]);  #else  		sep = '/'; -		char *env = getenv("TMPDIR"); - -		utf8path = env ? env : "/tmp/"; +		utf8path = LLStringUtil::getenv("TMPDIR", "/tmp/");  #endif  		if (utf8path[utf8path.size() - 1] != sep)  		{ diff --git a/indra/llcommon/llinitparam.h b/indra/llcommon/llinitparam.h index f1f4226c40..7f5b9b4ac2 100644 --- a/indra/llcommon/llinitparam.h +++ b/indra/llcommon/llinitparam.h @@ -2115,6 +2115,9 @@ namespace LLInitParam  			typedef typename super_t::iterator										iterator;  			typedef typename super_t::const_iterator								const_iterator; +			using super_t::operator(); +			using super_t::operator const container_t&; +  			explicit Multiple(const char* name = "")  			:	super_t(DERIVED_BLOCK::getBlockDescriptor(), name, container_t(), &validate, RANGE::minCount, RANGE::maxCount)  			{} diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp index c87d2a3e58..cf8f8cc6a5 100644 --- a/indra/llcommon/llleap.cpp +++ b/indra/llcommon/llleap.cpp @@ -47,9 +47,9 @@ class LLLeapImpl: public LLLeap      LOG_CLASS(LLLeap);  public:      // Called only by LLLeap::create() -    LLLeapImpl(const std::string& desc, const std::vector<std::string>& plugin): +    LLLeapImpl(const LLProcess::Params& cparams):          // We might reassign mDesc in the constructor body if it's empty here. -        mDesc(desc), +        mDesc(cparams.desc),          // We expect multiple LLLeapImpl instances. Definitely tweak          // mDonePump's name for uniqueness.          mDonePump("LLLeap", true), @@ -67,17 +67,17 @@ public:          // this class or method name.          mListener(new LLLeapListener(boost::bind(&LLLeapImpl::connect, this, _1, _2)))      { -        // Rule out empty vector -        if (plugin.empty()) +        // Rule out unpopulated Params block +        if (! cparams.executable.isProvided())          {              LLTHROW(Error("no plugin command"));          }          // Don't leave desc empty either, but in this case, if we weren't          // given one, we'll fake one. -        if (desc.empty()) +        if (mDesc.empty())          { -            mDesc = LLProcess::basename(plugin[0]); +            mDesc = LLProcess::basename(cparams.executable);              // how about a toLower() variant that returns the transformed string?!              std::string desclower(mDesc);              LLStringUtil::toLower(desclower); @@ -87,9 +87,9 @@ public:              // notice Python specially: we provide Python LLSD serialization              // support, so there's a pretty good reason to implement plugins              // in that language. -            if (plugin.size() >= 2 && (desclower == "python" || desclower == "python.exe")) +            if (cparams.args.size() && (desclower == "python" || desclower == "python.exe"))              { -                mDesc = LLProcess::basename(plugin[1]); +                mDesc = LLProcess::basename(cparams.args()[0]);              }          } @@ -97,14 +97,10 @@ public:          mDonePump.listen("LLLeap", boost::bind(&LLLeapImpl::bad_launch, this, _1));          // Okay, launch child. -        LLProcess::Params params; +        // Get a modifiable copy of params block to set files and postend. +        LLProcess::Params params(cparams); +        // copy our deduced mDesc back into the params block          params.desc = mDesc; -        std::vector<std::string>::const_iterator pi(plugin.begin()), pend(plugin.end()); -        params.executable = *pi++; -        for ( ; pi != pend; ++pi) -        { -            params.args.add(*pi); -        }          params.files.add(LLProcess::FileParam("pipe")); // stdin          params.files.add(LLProcess::FileParam("pipe")); // stdout          params.files.add(LLProcess::FileParam("pipe")); // stderr @@ -429,17 +425,17 @@ private:      boost::scoped_ptr<LLLeapListener> mListener;  }; -// This must follow the declaration of LLLeapImpl, so it may as well be last. -LLLeap* LLLeap::create(const std::string& desc, const std::vector<std::string>& plugin, bool exc) +// These must follow the declaration of LLLeapImpl, so they may as well be last. +LLLeap* LLLeap::create(const LLProcess::Params& params, bool exc)  {      // If caller is willing to permit exceptions, just instantiate.      if (exc) -        return new LLLeapImpl(desc, plugin); +        return new LLLeapImpl(params);      // Caller insists on suppressing LLLeap::Error. Very well, catch it.      try      { -        return new LLLeapImpl(desc, plugin); +        return new LLLeapImpl(params);      }      catch (const LLLeap::Error&)      { @@ -447,6 +443,23 @@ LLLeap* LLLeap::create(const std::string& desc, const std::vector<std::string>&      }  } +LLLeap* LLLeap::create(const std::string& desc, const std::vector<std::string>& plugin, bool exc) +{ +    LLProcess::Params params; +    params.desc = desc; +    std::vector<std::string>::const_iterator pi(plugin.begin()), pend(plugin.end()); +    // could validate here, but let's rely on LLLeapImpl's constructor +    if (pi != pend) +    { +        params.executable = *pi++; +    } +    for ( ; pi != pend; ++pi) +    { +        params.args.add(*pi); +    } +    return create(params, exc); +} +  LLLeap* LLLeap::create(const std::string& desc, const std::string& plugin, bool exc)  {      // Use LLStringUtil::getTokens() to parse the command line diff --git a/indra/llcommon/llleap.h b/indra/llcommon/llleap.h index 8aac8a64c5..7cecdf2f8f 100644 --- a/indra/llcommon/llleap.h +++ b/indra/llcommon/llleap.h @@ -14,6 +14,7 @@  #include "llinstancetracker.h"  #include "llexception.h" +#include "llprocess.h"  #include <string>  #include <vector> @@ -62,6 +63,19 @@ public:                            bool exc=true);      /** +     * Pass an LLProcess::Params instance to specify desc, executable, args et al. +     * +     * Note that files and postend are set implicitly; any values you set in +     * those fields will be disregarded. +     * +     * Pass exc=false to suppress LLLeap::Error exception. Obviously in that +     * case the caller cannot discover the nature of the error, merely that an +     * error of some kind occurred (because create() returned NULL). Either +     * way, the error is logged. +     */ +    static LLLeap* create(const LLProcess::Params& params, bool exc=true); + +    /**       * Exception thrown for invalid create() arguments, e.g. no plugin       * program. This is more resiliant than an LL_ERRS failure, because the       * string(s) passed to create() might come from an external source. This diff --git a/indra/llcommon/llpreprocessor.h b/indra/llcommon/llpreprocessor.h index 2879038c36..e8f9981437 100644 --- a/indra/llcommon/llpreprocessor.h +++ b/indra/llcommon/llpreprocessor.h @@ -101,6 +101,9 @@  #endif +// Although thread_local is now a standard storage class, we can't just +// #define LL_THREAD_LOCAL as thread_local because the *usage* is different. +// We'll have to take the time to change LL_THREAD_LOCAL declarations by hand.  #if LL_WINDOWS  # define LL_THREAD_LOCAL __declspec(thread)  #else @@ -177,6 +180,24 @@  #define LL_DLLIMPORT  #endif // LL_WINDOWS +#if ! defined(LL_WINDOWS) +#define LL_WCHAR_T_NATIVE 1 +#else  // LL_WINDOWS +// https://docs.microsoft.com/en-us/cpp/preprocessor/predefined-macros +// _WCHAR_T_DEFINED is defined if wchar_t is provided at all. +// Specifically, it has value 1 if wchar_t is an intrinsic type, else empty. +// _NATIVE_WCHAR_T_DEFINED has value 1 if wchar_t is intrinsic, else undefined. +// For years we have compiled with /Zc:wchar_t-, meaning that wchar_t is a +// typedef for unsigned short (in stddef.h). Lore has it that one of our +// proprietary binary-only libraries has traditionally been built that way and +// therefore EVERYTHING ELSE requires it. Therefore, in a typical Linden +// Windows build, _WCHAR_T_DEFINED is defined but empty, while +// _NATIVE_WCHAR_T_DEFINED is undefined. +# if defined(_NATIVE_WCHAR_T_DEFINED) +#  define LL_WCHAR_T_NATIVE 1 +# endif // _NATIVE_WCHAR_T_DEFINED +#endif // LL_WINDOWS +  #if LL_COMMON_LINK_SHARED  // CMake automagically defines llcommon_EXPORTS only when building llcommon  // sources, and only when llcommon is a shared library (i.e. when @@ -198,6 +219,8 @@  #define LL_TO_STRING_HELPER(x) #x  #define LL_TO_STRING(x) LL_TO_STRING_HELPER(x) +#define LL_TO_WSTRING_HELPER(x) L#x +#define LL_TO_WSTRING(x) LL_TO_WSTRING_HELPER(x)  #define LL_FILE_LINENO_MSG(msg) __FILE__ "(" LL_TO_STRING(__LINE__) ") : " msg  #define LL_GLUE_IMPL(x, y) x##y  #define LL_GLUE_TOKENS(x, y) LL_GLUE_IMPL(x, y) diff --git a/indra/llcommon/llprocess.cpp b/indra/llcommon/llprocess.cpp index 5753efdc59..1fa53f322b 100644 --- a/indra/llcommon/llprocess.cpp +++ b/indra/llcommon/llprocess.cpp @@ -1205,30 +1205,9 @@ static LLProcess::Status interpret_status(int status)  /// GetLastError()/FormatMessage() boilerplate  static std::string WindowsErrorString(const std::string& operation)  { -	int result = GetLastError(); - -	LPTSTR error_str = 0; -	if (FormatMessage( FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, -					   NULL, -					   result, -					   0, -					   (LPTSTR)&error_str, -					   0, -					   NULL) -		!= 0)  -	{ -		// convert from wide-char string to multi-byte string -		char message[256]; -		wcstombs(message, error_str, sizeof(message)); -		message[sizeof(message)-1] = 0; -		LocalFree(error_str); -		// convert to std::string to trim trailing whitespace -		std::string mbsstr(message); -		mbsstr.erase(mbsstr.find_last_not_of(" \t\r\n")); -		return STRINGIZE(operation << " failed (" << result << "): " << mbsstr); -	} -	return STRINGIZE(operation << " failed (" << result -					 << "), but FormatMessage() did not explain"); +	auto result = GetLastError(); +	return STRINGIZE(operation << " failed (" << result << "): " +					 << windows_message<std::string>(result));  }  /***************************************************************************** diff --git a/indra/llcommon/llstring.cpp b/indra/llcommon/llstring.cpp index 9a02fecd72..0174c411b4 100644 --- a/indra/llcommon/llstring.cpp +++ b/indra/llcommon/llstring.cpp @@ -30,6 +30,7 @@  #include "llerror.h"  #include "llfasttimer.h"  #include "llsd.h" +#include <vector>  #if LL_WINDOWS  #include "llwin32headerslean.h" @@ -672,6 +673,11 @@ namespace snprintf_hack  	}  } +std::string ll_convert_wide_to_string(const wchar_t* in) +{ +	return ll_convert_wide_to_string(in, CP_UTF8); +} +  std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page)  {  	std::string out; @@ -709,7 +715,12 @@ std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page)  	return out;  } -wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page) +std::wstring ll_convert_string_to_wide(const std::string& in) +{ +	return ll_convert_string_to_wide(in, CP_UTF8); +} + +std::wstring ll_convert_string_to_wide(const std::string& in, unsigned int code_page)  {  	// From review:  	// We can preallocate a wide char buffer that is the same length (in wchar_t elements) as the utf8 input, @@ -719,28 +730,148 @@ wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page  	// but we *are* seeing string operations taking a bunch of time, especially when constructing widgets.  //	int output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), NULL, 0); -	// reserve place to NULL terminator -	int output_str_len = in.length(); -	wchar_t* w_out = new wchar_t[output_str_len + 1]; +	// reserve an output buffer that will be destroyed on exit, with a place +	// to put NULL terminator +	std::vector<wchar_t> w_out(in.length() + 1); -	memset(w_out, 0, output_str_len + 1); -	int real_output_str_len = MultiByteToWideChar (code_page, 0, in.c_str(), in.length(), w_out, output_str_len); +	memset(&w_out[0], 0, w_out.size()); +	int real_output_str_len = MultiByteToWideChar(code_page, 0, in.c_str(), in.length(), +												  &w_out[0], w_out.size() - 1);  	//looks like MultiByteToWideChar didn't add null terminator to converted string, see EXT-4858.  	w_out[real_output_str_len] = 0; -	return w_out; +	// construct string<wchar_t> from our temporary output buffer +	return {&w_out[0]}; +} + +LLWString ll_convert_wide_to_wstring(const std::wstring& in) +{ +    // This function, like its converse, is a placeholder, encapsulating a +    // guilty little hack: the only "official" way nat has found to convert +    // between std::wstring (16 bits on Windows) and LLWString (UTF-32) is +    // by using iconv, which we've avoided so far. It kinda sorta works to +    // just copy individual characters... +    // The point is that if/when we DO introduce some more official way to +    // perform such conversions, we should only have to call it here. +    return { in.begin(), in.end() }; +} + +std::wstring ll_convert_wstring_to_wide(const LLWString& in) +{ +    // See comments in ll_convert_wide_to_wstring() +    return { in.begin(), in.end() };  }  std::string ll_convert_string_to_utf8_string(const std::string& in)  { -	wchar_t* w_mesg = ll_convert_string_to_wide(in, CP_ACP); -	std::string out_utf8(ll_convert_wide_to_string(w_mesg, CP_UTF8)); -	delete[] w_mesg; +	auto w_mesg = ll_convert_string_to_wide(in, CP_ACP); +	std::string out_utf8(ll_convert_wide_to_string(w_mesg.c_str(), CP_UTF8));  	return out_utf8;  } -#endif // LL_WINDOWS + +namespace +{ + +void HeapFree_deleter(void* ptr) +{ +    // instead of LocalFree(), per https://stackoverflow.com/a/31541205 +    HeapFree(GetProcessHeap(), NULL, ptr); +} + +} // anonymous namespace + +template<> +std::wstring windows_message<std::wstring>(DWORD error) +{ +    // derived from https://stackoverflow.com/a/455533 +    wchar_t* rawptr = nullptr; +    auto okay = FormatMessageW( +        // use system message tables for GetLastError() codes +        FORMAT_MESSAGE_FROM_SYSTEM | +        // internally allocate buffer and return its pointer +        FORMAT_MESSAGE_ALLOCATE_BUFFER | +        // you cannot pass insertion parameters (thanks Gandalf) +        FORMAT_MESSAGE_IGNORE_INSERTS | +        // ignore line breaks in message definition text +        FORMAT_MESSAGE_MAX_WIDTH_MASK, +        NULL,                       // lpSource, unused with FORMAT_MESSAGE_FROM_SYSTEM +        error,                      // dwMessageId +        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // dwLanguageId +        (LPWSTR)&rawptr,         // lpBuffer: force-cast wchar_t** to wchar_t* +        0,                // nSize, unused with FORMAT_MESSAGE_ALLOCATE_BUFFER +        NULL);            // Arguments, unused + +    // make a unique_ptr from rawptr so it gets cleaned up properly +    std::unique_ptr<wchar_t, void(*)(void*)> bufferptr(rawptr, HeapFree_deleter); + +    if (okay && bufferptr) +    { +        // got the message, return it ('okay' is length in characters) +        return { bufferptr.get(), okay }; +    } + +    // did not get the message, synthesize one +    auto format_message_error = GetLastError(); +    std::wostringstream out; +    out << L"GetLastError() " << error << L" (FormatMessageW() failed with " +        << format_message_error << L")"; +    return out.str(); +} + +boost::optional<std::wstring> llstring_getoptenv(const std::string& key) +{ +    auto wkey = ll_convert_string_to_wide(key); +    // Take a wild guess as to how big the buffer should be. +    std::vector<wchar_t> buffer(1024); +    auto n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); +    // If our initial guess was too short, n will indicate the size (in +    // wchar_t's) that buffer should have been, including the terminating nul. +    if (n > (buffer.size() - 1)) +    { +        // make it big enough +        buffer.resize(n); +        // and try again +        n = GetEnvironmentVariableW(wkey.c_str(), &buffer[0], buffer.size()); +    } +    // did that (ultimately) succeed? +    if (n) +    { +        // great, return populated boost::optional +        return boost::optional<std::wstring>(&buffer[0]); +    } + +    // not successful +    auto last_error = GetLastError(); +    // Don't bother warning for NOT_FOUND; that's an expected case +    if (last_error != ERROR_ENVVAR_NOT_FOUND) +    { +        LL_WARNS() << "GetEnvironmentVariableW('" << key << "') failed: " +                   << windows_message<std::string>(last_error) << LL_ENDL; +    } +    // return empty boost::optional +    return {}; +} + +#else  // ! LL_WINDOWS + +boost::optional<std::string> llstring_getoptenv(const std::string& key) +{ +    auto found = getenv(key.c_str()); +    if (found) +    { +        // return populated boost::optional +        return boost::optional<std::string>(found); +    } +    else +    { +        // return empty boost::optional +        return {}; +    } +} + +#endif // ! LL_WINDOWS  long LLStringOps::sPacificTimeOffset = 0;  long LLStringOps::sLocalTimeOffset = 0; diff --git a/indra/llcommon/llstring.h b/indra/llcommon/llstring.h index 68ee9db46b..30bec3a6f8 100644 --- a/indra/llcommon/llstring.h +++ b/indra/llcommon/llstring.h @@ -27,6 +27,7 @@  #ifndef LL_LLSTRING_H  #define LL_LLSTRING_H +#include <boost/optional/optional.hpp>  #include <string>  #include <cstdio>  //#include <locale> @@ -337,6 +338,19 @@ public:  		const string_type& string,  		const string_type& substr); +	/** +	 * get environment string value with proper Unicode handling +	 * (key is always UTF-8) +	 * detect absence by return value == dflt +	 */ +	static string_type getenv(const std::string& key, const string_type& dflt=""); +	/** +	 * get optional environment string value with proper Unicode handling +	 * (key is always UTF-8) +	 * detect absence by (! return value) +	 */ +	static boost::optional<string_type> getoptenv(const std::string& key); +  	static void	addCRLF(string_type& string);  	static void	removeCRLF(string_type& string);  	static void removeWindowsCR(string_type& string); @@ -496,6 +510,37 @@ LL_COMMON_API bool iswindividual(llwchar elem);   * Unicode support   */ +/// generic conversion aliases +template<typename TO, typename FROM, typename Enable=void> +struct ll_convert_impl +{ +    // Don't even provide a generic implementation. We specialize for every +    // combination we do support. +    TO operator()(const FROM& in) const; +}; + +// Use a function template to get the nice ll_convert<TO>(from_value) API. +template<typename TO, typename FROM> +TO ll_convert(const FROM& in) +{ +    return ll_convert_impl<TO, FROM>()(in); +} + +// degenerate case +template<typename T> +struct ll_convert_impl<T, T> +{ +    T operator()(const T& in) const { return in; } +}; + +// specialize ll_convert_impl<TO, FROM> to return EXPR +#define ll_convert_alias(TO, FROM, EXPR)                    \ +template<>                                                  \ +struct ll_convert_impl<TO, FROM>                            \ +{                                                           \ +    TO operator()(const FROM& in) const { return EXPR; }    \ +} +  // Make the incoming string a utf8 string. Replaces any unknown glyph  // with the UNKNOWN_CHARACTER. Once any unknown glyph is found, the rest  // of the data may not be recovered. @@ -503,30 +548,88 @@ LL_COMMON_API std::string rawstr_to_utf8(const std::string& raw);  //  // We should never use UTF16 except when communicating with Win32! +// https://docs.microsoft.com/en-us/cpp/cpp/char-wchar-t-char16-t-char32-t +// nat 2018-12-14: I consider the whole llutf16string thing a mistake, because +// the Windows APIs we want to call are all defined in terms of wchar_t* +// (or worse, LPCTSTR). +// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types + +// While there is no point coding for an ASCII-only world (! defined(UNICODE)), +// use of U16 and llutf16string for Windows APIs locks in /Zc:wchar_t-. Going +// forward, we should code in terms of wchar_t and std::wstring so as to +// support either setting of /Zc:wchar_t. + +// The first link above states that char can be used to hold ASCII or any +// multi-byte character set, and distinguishes wchar_t (UTF-16LE), char16_t +// (UTF-16) and char32_t (UTF-32). Nonetheless, within this code base: +// * char and std::string always hold UTF-8 (of which ASCII is a subset). It +//   is a BUG if they are used to pass strings in any other multi-byte +//   encoding. +// * wchar_t and std::wstring should be our interface to Windows wide-string +//   APIs, and therefore hold UTF-16LE. +// * U16 and llutf16string are the previous but DEPRECATED UTF-16LE type. Do +//   not introduce new uses of U16 or llutf16string for string data. +// * llwchar and LLWString hold UTF-32 strings. +// * Do not introduce char16_t or std::u16string. +// * Do not introduce char32_t or std::u32string.  // +// This typedef may or may not be identical to std::wstring, depending on +// LL_WCHAR_T_NATIVE.  typedef std::basic_string<U16> llutf16string; +#if ! defined(LL_WCHAR_T_NATIVE) +// wchar_t is identical to U16, and std::wstring is identical to llutf16string. +// Defining an ll_convert alias involving llutf16string would collide with the +// comparable preferred alias involving std::wstring. (In this scenario, if +// you pass llutf16string, it will engage the std::wstring specialization.) +#define ll_convert_u16_alias(TO, FROM, EXPR) // nothing +#else  // defined(LL_WCHAR_T_NATIVE) +// wchar_t is a distinct native type, so llutf16string is also a distinct +// type, and there IS a point to converting separately to/from llutf16string. +// (But why? Windows APIs are still defined in terms of wchar_t, and +// in this scenario llutf16string won't work for them!) +#define ll_convert_u16_alias(TO, FROM, EXPR) ll_convert_alias(TO, FROM, EXPR) + +#if LL_WINDOWS +// LL_WCHAR_T_NATIVE is defined on non-Windows systems because, in fact, +// wchar_t is native. Everywhere but Windows, we use it for llwchar (see +// stdtypes.h). That makes LLWString identical to std::wstring, so these +// aliases for std::wstring would collide with those for LLWString. Only +// define on Windows, where converting between std::wstring and llutf16string +// means copying chars. +ll_convert_alias(llutf16string, std::wstring, llutf16string(in.begin(), in.end())); +ll_convert_alias(std::wstring, llutf16string,  std::wstring(in.begin(), in.end())); +#endif // LL_WINDOWS +#endif // defined(LL_WCHAR_T_NATIVE) +  LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str, S32 len);  LL_COMMON_API LLWString utf16str_to_wstring(const llutf16string &utf16str); +ll_convert_u16_alias(LLWString, llutf16string, utf16str_to_wstring(in));  LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str, S32 len);  LL_COMMON_API llutf16string wstring_to_utf16str(const LLWString &utf32str); +ll_convert_u16_alias(llutf16string, LLWString, wstring_to_utf16str(in));  LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str, S32 len);  LL_COMMON_API llutf16string utf8str_to_utf16str ( const std::string& utf8str ); +ll_convert_u16_alias(llutf16string, std::string, utf8str_to_utf16str(in));  LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str, S32 len);  LL_COMMON_API LLWString utf8str_to_wstring(const std::string &utf8str);  // Same function, better name. JC  inline LLWString utf8string_to_wstring(const std::string& utf8_string) { return utf8str_to_wstring(utf8_string); } +// best name of all +ll_convert_alias(LLWString, std::string, utf8string_to_wstring(in));  //  LL_COMMON_API S32 wchar_to_utf8chars(llwchar inchar, char* outchars);  LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str, S32 len);  LL_COMMON_API std::string wstring_to_utf8str(const LLWString &utf32str); +ll_convert_alias(std::string, LLWString, wstring_to_utf8str(in));  LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str, S32 len);  LL_COMMON_API std::string utf16str_to_utf8str(const llutf16string &utf16str); +ll_convert_u16_alias(std::string, llutf16string, utf16str_to_utf8str(in));  #if LL_WINDOWS  inline std::string wstring_to_utf8str(const llutf16string &utf16str) { return utf16str_to_utf8str(utf16str);} @@ -635,22 +738,77 @@ using snprintf_hack::snprintf;   * This replaces the unsafe W2A macro from ATL.   */  LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in, unsigned int code_page); +LL_COMMON_API std::string ll_convert_wide_to_string(const wchar_t* in); // default CP_UTF8 +inline std::string ll_convert_wide_to_string(const std::wstring& in, unsigned int code_page) +{ +    return ll_convert_wide_to_string(in.c_str(), code_page); +} +inline std::string ll_convert_wide_to_string(const std::wstring& in) +{ +    return ll_convert_wide_to_string(in.c_str()); +} +ll_convert_alias(std::string, std::wstring, ll_convert_wide_to_string(in));  /**   * Converts a string to wide string. - * - * It will allocate memory for result string with "new []". Don't forget to release it with "delete []".   */ -LL_COMMON_API wchar_t* ll_convert_string_to_wide(const std::string& in, unsigned int code_page); +LL_COMMON_API std::wstring ll_convert_string_to_wide(const std::string& in, +                                                     unsigned int code_page); +LL_COMMON_API std::wstring ll_convert_string_to_wide(const std::string& in); +                                                     // default CP_UTF8 +ll_convert_alias(std::wstring, std::string, ll_convert_string_to_wide(in));  /** - * Converts incoming string into urf8 string + * Convert a Windows wide string to our LLWString + */ +LL_COMMON_API LLWString ll_convert_wide_to_wstring(const std::wstring& in); +ll_convert_alias(LLWString, std::wstring, ll_convert_wide_to_wstring(in)); + +/** + * Convert LLWString to Windows wide string + */ +LL_COMMON_API std::wstring ll_convert_wstring_to_wide(const LLWString& in); +ll_convert_alias(std::wstring, LLWString, ll_convert_wstring_to_wide(in)); + +/** + * Converts incoming string into utf8 string   *   */  LL_COMMON_API std::string ll_convert_string_to_utf8_string(const std::string& in); +/// Get Windows message string for passed GetLastError() code +// VS 2013 doesn't let us forward-declare this template, which is what we +// started with, so the implementation could reference the specialization we +// haven't yet declared. Somewhat weirdly, just stating the generic +// implementation in terms of the specialization works, even in this order... + +// the general case is just a conversion from the sole implementation +// Microsoft says DWORD is a typedef for unsigned long +// https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types +// so rather than drag windows.h into everybody's include space... +template<typename STRING> +STRING windows_message(unsigned long error) +{ +    return ll_convert<STRING>(windows_message<std::wstring>(error)); +} + +/// There's only one real implementation +template<> +LL_COMMON_API std::wstring windows_message<std::wstring>(unsigned long error); + +/// Get Windows message string, implicitly calling GetLastError() +template<typename STRING> +STRING windows_message() { return windows_message<STRING>(GetLastError()); } +  //@} -#endif // LL_WINDOWS + +LL_COMMON_API boost::optional<std::wstring> llstring_getoptenv(const std::string& key); + +#else // ! LL_WINDOWS + +LL_COMMON_API boost::optional<std::string>  llstring_getoptenv(const std::string& key); + +#endif // ! LL_WINDOWS  /**   * Many of the 'strip' and 'replace' methods of LLStringUtilBase need @@ -1593,6 +1751,37 @@ bool LLStringUtilBase<T>::endsWith(  	return (idx == (string.size() - substr.size()));  } +// static +template<class T> +auto LLStringUtilBase<T>::getoptenv(const std::string& key) -> boost::optional<string_type> +{ +    auto found(llstring_getoptenv(key)); +    if (found) +    { +        // return populated boost::optional +        return { ll_convert<string_type>(*found) }; +    } +    else +    { +        // empty boost::optional +        return {}; +    } +} + +// static +template<class T> +auto LLStringUtilBase<T>::getenv(const std::string& key, const string_type& dflt) -> string_type +{ +    auto found(getoptenv(key)); +    if (found) +    { +        return *found; +    } +    else +    { +        return dflt; +    } +}  template<class T>   BOOL LLStringUtilBase<T>::convertToBOOL(const string_type& string, BOOL& value) diff --git a/indra/llcommon/stdtypes.h b/indra/llcommon/stdtypes.h index bf3f3f9ee8..6c9871e76c 100644 --- a/indra/llcommon/stdtypes.h +++ b/indra/llcommon/stdtypes.h @@ -37,7 +37,12 @@ typedef signed int			S32;  typedef unsigned int			U32;  #if LL_WINDOWS -// Windows wchar_t is 16-bit +// https://docs.microsoft.com/en-us/cpp/build/reference/zc-wchar-t-wchar-t-is-native-type +// https://docs.microsoft.com/en-us/cpp/cpp/fundamental-types-cpp +// Windows wchar_t is 16-bit, whichever way /Zc:wchar_t is set. In effect, +// Windows wchar_t is always a typedef, either for unsigned short or __wchar_t. +// (__wchar_t, available either way, is Microsoft's native 2-byte wchar_t type.) +// In any case, llwchar should be a UTF-32 type.  typedef U32				llwchar;  #else  typedef wchar_t				llwchar; diff --git a/indra/llcommon/stringize.h b/indra/llcommon/stringize.h index a5a90d7297..38dd198ad3 100644 --- a/indra/llcommon/stringize.h +++ b/indra/llcommon/stringize.h @@ -30,7 +30,6 @@  #define LL_STRINGIZE_H  #include <sstream> -#include <boost/phoenix/phoenix.hpp>  #include <llstring.h>  /** @@ -53,12 +52,7 @@ std::basic_string<CHARTYPE> gstringize(const T& item)   */  inline std::string stringize(const std::wstring& item)  { -    LL_WARNS() << "WARNING:  Possible narrowing" << LL_ENDL; -     -    std::string s; -     -    s = wstring_to_utf8str(item); -    return gstringize<char>(s); +    return wstring_to_utf8str(item);  }  /** @@ -76,7 +70,10 @@ std::string stringize(const T& item)   */  inline std::wstring wstringize(const std::string& item)  { -    return gstringize<wchar_t>(item.c_str()); +    // utf8str_to_wstring() returns LLWString, which isn't necessarily the +    // same as std::wstring +    LLWString s(utf8str_to_wstring(item)); +    return std::wstring(s.begin(), s.end());  }  /** @@ -91,10 +88,10 @@ std::wstring wstringize(const T& item)  /**   * stringize_f(functor)   */ -template <typename Functor> -std::string stringize_f(Functor const & f) +template <typename CHARTYPE, typename Functor> +std::basic_string<CHARTYPE> stringize_f(Functor const & f)  { -    std::ostringstream out; +    std::basic_ostringstream<CHARTYPE> out;      f(out);      return out.str();  } @@ -108,31 +105,37 @@ std::string stringize_f(Functor const & f)   * return out.str();   * @endcode   */ -#define STRINGIZE(EXPRESSION) (stringize_f(boost::phoenix::placeholders::arg1 << EXPRESSION)) +#define STRINGIZE(EXPRESSION) (stringize_f<char>([&](std::ostream& out){ out << EXPRESSION; })) +/** + * WSTRINGIZE() is the wstring equivalent of STRINGIZE() + */ +#define WSTRINGIZE(EXPRESSION) (stringize_f<wchar_t>([&](std::wostream& out){ out << EXPRESSION; }))  /**   * destringize(str)   * defined for symmetry with stringize - * *NOTE - this has distinct behavior from boost::lexical_cast<T> regarding + * @NOTE - this has distinct behavior from boost::lexical_cast<T> regarding   * leading/trailing whitespace and handling of bad_lexical_cast exceptions + * @NOTE - no need for dewstringize(), since passing std::wstring will Do The + * Right Thing   */ -template <typename T> -T destringize(std::string const & str) +template <typename T, typename CHARTYPE> +T destringize(std::basic_string<CHARTYPE> const & str)  { -	T val; -    std::istringstream in(str); -	in >> val; +    T val; +    std::basic_istringstream<CHARTYPE> in(str); +    in >> val;      return val;  }  /**   * destringize_f(str, functor)   */ -template <typename Functor> -void destringize_f(std::string const & str, Functor const & f) +template <typename CHARTYPE, typename Functor> +void destringize_f(std::basic_string<CHARTYPE> const & str, Functor const & f)  { -    std::istringstream in(str); +    std::basic_istringstream<CHARTYPE> in(str);      f(in);  } @@ -143,8 +146,11 @@ void destringize_f(std::string const & str, Functor const & f)   * std::istringstream in(str);   * in >> item1 >> item2 >> item3 ... ;   * @endcode + * @NOTE - once we get generic lambdas, we shouldn't need DEWSTRINGIZE() any + * more since DESTRINGIZE() should do the right thing with a std::wstring. But + * until then, the lambda we pass must accept the right std::basic_istream.   */ -#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), (boost::phoenix::placeholders::arg1 >> EXPRESSION))) - +#define DESTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](std::istream& in){in >> EXPRESSION;})) +#define DEWSTRINGIZE(STR, EXPRESSION) (destringize_f((STR), [&](std::wistream& in){in >> EXPRESSION;}))  #endif /* ! defined(LL_STRINGIZE_H) */ diff --git a/indra/llcommon/tests/llerror_test.cpp b/indra/llcommon/tests/llerror_test.cpp index ce0dbce075..8e1f4c14ac 100644 --- a/indra/llcommon/tests/llerror_test.cpp +++ b/indra/llcommon/tests/llerror_test.cpp @@ -78,8 +78,12 @@ namespace tut  	class TestRecorder : public LLError::Recorder  	{  	public: -		TestRecorder() { mWantsTime = false; mWantsTags = true; } -		virtual ~TestRecorder() {  } +		TestRecorder() +            { +                showTime(false); +            } +		virtual ~TestRecorder() +            {}  		virtual void recordMessage(LLError::ELevel level,  						   const std::string& message) @@ -90,8 +94,6 @@ namespace tut  		int countMessages()			{ return (int) mMessages.size(); }  		void clearMessages()		{ mMessages.clear(); } -		void setWantsTime(bool t)	{ mWantsTime = t; } -  		std::string message(int n)  		{  			std::ostringstream test_name; @@ -139,9 +141,14 @@ namespace tut  		}  		void setWantsTime(bool t) -		{ -			boost::dynamic_pointer_cast<TestRecorder>(mRecorder)->setWantsTime(t); -		} +            { +                boost::dynamic_pointer_cast<TestRecorder>(mRecorder)->showTime(t); +            } + +		void setWantsMultiline(bool t) +            { +                boost::dynamic_pointer_cast<TestRecorder>(mRecorder)->showMultiline(t); +            }  		std::string message(int n)  		{ @@ -378,27 +385,6 @@ namespace  	}  } -namespace tut -{ -	template<> template<> -	void ErrorTestObject::test<5>() -		// file and line information in log messages -	{ -		std::string location = writeReturningLocation(); -			// expecting default to not print location information - -		LLError::setPrintLocation(true); -		writeReturningLocation(); - -		LLError::setPrintLocation(false); -		writeReturningLocation(); - -		ensure_message_does_not_contain(0, location); -		ensure_message_field_equals(1, LOCATION_FIELD, location); -		ensure_message_does_not_contain(2, location); -	} -} -  /* The following helper functions and class members all log a simple message  	from some particular function scope.  Each function takes a bool argument  	that indicates if it should log its own name or not (in the manner that @@ -512,6 +498,39 @@ namespace  	}  } +namespace +{ +    void writeMsgNeedsEscaping() +    { +        LL_DEBUGS("WriteTag") << "backslash\\" << LL_ENDL; +        LL_INFOS("WriteTag") << "newline\nafternewline" << LL_ENDL; +        LL_WARNS("WriteTag") << "return\rafterreturn" << LL_ENDL; + +        LL_DEBUGS("WriteTag") << "backslash\\backslash\\" << LL_ENDL; +        LL_INFOS("WriteTag") << "backslash\\newline\nanothernewline\nafternewline" << LL_ENDL; +        LL_WARNS("WriteTag") << "backslash\\returnnewline\r\n\\afterbackslash" << LL_ENDL; +    } +}; + +namespace tut +{ +    template<> template<> +    void ErrorTestObject::test<5>() +        // backslash, return, and newline are not escaped with backslashes +    { +        LLError::setDefaultLevel(LLError::LEVEL_DEBUG); +        setWantsMultiline(true);  +        writeMsgNeedsEscaping(); // but should not be now +        ensure_message_field_equals(0, MSG_FIELD, "backslash\\"); +        ensure_message_field_equals(1, MSG_FIELD, "newline\nafternewline"); +        ensure_message_field_equals(2, MSG_FIELD, "return\rafterreturn"); +        ensure_message_field_equals(3, MSG_FIELD, "backslash\\backslash\\"); +        ensure_message_field_equals(4, MSG_FIELD, "backslash\\newline\nanothernewline\nafternewline"); +        ensure_message_field_equals(5, MSG_FIELD, "backslash\\returnnewline\r\n\\afterbackslash"); +        ensure_message_count(6); +    } +} +  namespace tut  {  	template<> template<> @@ -583,7 +602,6 @@ namespace tut  		// special handling of LL_ERRS() calls  	void ErrorTestObject::test<8>()  	{ -		LLError::setPrintLocation(false);  		std::string location = errorReturningLocation();  		ensure_message_field_equals(0, LOCATION_FIELD, location); @@ -630,15 +648,15 @@ namespace tut  		// output order  	void ErrorTestObject::test<10>()  	{ -		LLError::setPrintLocation(true);  		LLError::setTimeFunction(roswell);  		setWantsTime(true); +  		std::string location,  					function;  		writeReturningLocationAndFunction(location, function);  		ensure_equals("order is time level tags location function message", -			message(0), +                      message(0),                        roswell() + " INFO " + "# " /* no tag */ + location + " " + function + " : " + "apple");  	} @@ -658,7 +676,7 @@ namespace tut  		LLError::setTimeFunction(roswell);  		LLError::RecorderPtr anotherRecorder(new TestRecorder()); -		boost::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->setWantsTime(true); +		boost::dynamic_pointer_cast<TestRecorder>(anotherRecorder)->showTime(true);  		LLError::addRecorder(anotherRecorder);  		LL_INFOS() << "baz" << LL_ENDL; @@ -835,20 +853,6 @@ namespace tut  	}  } -namespace -{ -    void writeMsgNeedsEscaping() -    { -        LL_DEBUGS("WriteTag") << "backslash\\" << LL_ENDL; -        LL_INFOS("WriteTag") << "newline\nafternewline" << LL_ENDL; -        LL_WARNS("WriteTag") << "return\rafterreturn" << LL_ENDL; - -        LL_DEBUGS("WriteTag") << "backslash\\backslash\\" << LL_ENDL; -        LL_INFOS("WriteTag") << "backslash\\newline\nanothernewline\nafternewline" << LL_ENDL; -        LL_WARNS("WriteTag") << "backslash\\returnnewline\r\n\\afterbackslash" << LL_ENDL; -    } -}; -  namespace tut  {      template<> template<> diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp index c387da6c48..45648536c4 100644 --- a/indra/llcommon/tests/llleap_test.cpp +++ b/indra/llcommon/tests/llleap_test.cpp @@ -26,6 +26,7 @@  #include "wrapllerrs.h"  #include "llevents.h"  #include "llprocess.h" +#include "llstring.h"  #include "stringize.h"  #include "StringVec.h"  #include <functional> @@ -198,14 +199,12 @@ namespace tut              // basename.              reader_module(LLProcess::basename(                                reader.getName().substr(0, reader.getName().length()-3))), -            pPYTHON(getenv("PYTHON")), -            PYTHON(pPYTHON? pPYTHON : "") +            PYTHON(LLStringUtil::getenv("PYTHON"))          { -            ensure("Set PYTHON to interpreter pathname", pPYTHON); +            ensure("Set PYTHON to interpreter pathname", !PYTHON.empty());          }          NamedExtTempFile reader;          const std::string reader_module; -        const char* pPYTHON;          const std::string PYTHON;      };      typedef test_group<llleap_data> llleap_group; diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp index b27e125d2e..5c87cdabd9 100644 --- a/indra/llcommon/tests/llprocess_test.cpp +++ b/indra/llcommon/tests/llprocess_test.cpp @@ -34,6 +34,7 @@  #include "stringize.h"  #include "llsdutil.h"  #include "llevents.h" +#include "llstring.h"  #include "wrapllerrs.h"  #if defined(LL_WINDOWS) @@ -142,8 +143,8 @@ struct PythonProcessLauncher          mDesc(desc),          mScript("py", script)      { -        const char* PYTHON(getenv("PYTHON")); -        tut::ensure("Set $PYTHON to the Python interpreter", PYTHON); +        auto PYTHON(LLStringUtil::getenv("PYTHON")); +        tut::ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty());          mParams.desc = desc + " script";          mParams.executable = PYTHON; diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp index 745e3a168c..6ac974e659 100644 --- a/indra/llcommon/tests/llsdserialize_test.cpp +++ b/indra/llcommon/tests/llsdserialize_test.cpp @@ -41,6 +41,7 @@ typedef U32 uint32_t;  #include <sys/stat.h>  #include <sys/wait.h>  #include "llprocess.h" +#include "llstring.h"  #endif  #include "boost/range.hpp" @@ -1705,8 +1706,8 @@ namespace tut          template <typename CONTENT>          void python(const std::string& desc, const CONTENT& script, int expect=0)          { -            const char* PYTHON(getenv("PYTHON")); -            ensure("Set $PYTHON to the Python interpreter", PYTHON); +            auto PYTHON(LLStringUtil::getenv("PYTHON")); +            ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty());              NamedTempFile scriptfile("py", script); @@ -1714,7 +1715,7 @@ namespace tut              std::string q("\"");              std::string qPYTHON(q + PYTHON + q);              std::string qscript(q + scriptfile.getName() + q); -            int rc = _spawnl(_P_WAIT, PYTHON, qPYTHON.c_str(), qscript.c_str(), NULL); +            int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), NULL);              if (rc == -1)              {                  char buffer[256]; diff --git a/indra/llcommon/tests/wrapllerrs.h b/indra/llcommon/tests/wrapllerrs.h index 9a4bbbd630..08fbf19b1c 100644 --- a/indra/llcommon/tests/wrapllerrs.h +++ b/indra/llcommon/tests/wrapllerrs.h @@ -109,6 +109,12 @@ public:          mMessages.push_back(message);      } +    friend inline +    std::ostream& operator<<(std::ostream& out, const CaptureLogRecorder& log) +    { +        return log.streamto(out); +    } +      /// Don't assume the message we want is necessarily the LAST log message      /// emitted by the underlying code; search backwards through all messages      /// for the sought string. @@ -126,7 +132,7 @@ public:          throw tut::failure(STRINGIZE("failed to find '" << search                                       << "' in captured log messages:\n" -                                     << boost::ref(*this))); +                                     << *this));      }      std::ostream& streamto(std::ostream& out) const @@ -200,10 +206,4 @@ private:  	LLError::RecorderPtr mRecorder;  }; -inline -std::ostream& operator<<(std::ostream& out, const CaptureLogRecorder& log) -{ -    return log.streamto(out); -} -  #endif /* ! defined(LL_WRAPLLERRS_H) */ diff --git a/indra/llimagej2coj/CMakeLists.txt b/indra/llimagej2coj/CMakeLists.txt index 97d22cf86a..c9423d50dd 100644 --- a/indra/llimagej2coj/CMakeLists.txt +++ b/indra/llimagej2coj/CMakeLists.txt @@ -29,7 +29,9 @@ set_source_files_properties(${llimagej2coj_HEADER_FILES}  list(APPEND llimagej2coj_SOURCE_FILES ${llimagej2coj_HEADER_FILES})  add_library (llimagej2coj ${llimagej2coj_SOURCE_FILES}) +  target_link_libraries(      llimagej2coj      ${OPENJPEG_LIBRARIES}      ) + diff --git a/indra/llmessage/tests/commtest.h b/indra/llmessage/tests/commtest.h index 7c8f27bbd2..0359eba803 100644 --- a/indra/llmessage/tests/commtest.h +++ b/indra/llmessage/tests/commtest.h @@ -34,6 +34,7 @@  #include "llsd.h"  #include "llhost.h"  #include "llexception.h" +#include "llstring.h"  #include "stringize.h"  #include <map>  #include <string> @@ -46,12 +47,7 @@ struct CommtestError: public LLException  static bool query_verbose()  { -    const char* cbose = getenv("INTEGRATION_TEST_VERBOSE"); -    if (! cbose) -    { -        cbose = "1"; -    } -    std::string strbose(cbose); +    std::string strbose(LLStringUtil::getenv("INTEGRATION_TEST_VERBOSE", "1"));      return (! (strbose == "0" || strbose == "off" ||                 strbose == "false" || strbose == "quiet"));  } diff --git a/indra/llmessage/tests/llhttpclient_test.cpp b/indra/llmessage/tests/llhttpclient_test.cpp index 9356a14f1f..78faa66a0d 100644 --- a/indra/llmessage/tests/llhttpclient_test.cpp +++ b/indra/llmessage/tests/llhttpclient_test.cpp @@ -41,6 +41,7 @@  #include "llpumpio.h"  #include "lliosocket.h" +#include "llstring.h"  #include "stringize.h"  #include "llcleanup.h" @@ -50,13 +51,13 @@ namespace tut  	{  	public:  		HTTPClientTestData(): -			PORT(getenv("PORT")), +			PORT(LLStringUtil::getenv("PORT")),  			// Turning NULL PORT into empty string doesn't make things work;  			// that's just to keep this initializer from blowing up. We test  			// PORT separately in the constructor body. -			local_server(STRINGIZE("http://127.0.0.1:" << (PORT? PORT : "") << "/")) +			local_server(STRINGIZE("http://127.0.0.1:" << PORT << "/"))  		{ -			ensure("Set environment variable PORT to local test server port", PORT); +			ensure("Set environment variable PORT to local test server port", !PORT.empty());  			apr_pool_create(&mPool, NULL);  			LLCurl::initClass(false);  			mClientPump = new LLPumpIO(mPool); @@ -87,7 +88,7 @@ namespace tut  			}  		} -		const char* const PORT; +		const std::string PORT;  		const std::string local_server;  	private: diff --git a/indra/llplugin/slplugin/CMakeLists.txt b/indra/llplugin/slplugin/CMakeLists.txt index 0e5e835777..33520ad64c 100644 --- a/indra/llplugin/slplugin/CMakeLists.txt +++ b/indra/llplugin/slplugin/CMakeLists.txt @@ -48,7 +48,7 @@ add_executable(SLPlugin      WIN32      MACOSX_BUNDLE      ${SLPlugin_SOURCE_FILES} -) +    )  if (WINDOWS)  set_target_properties(SLPlugin diff --git a/indra/llrender/llfontgl.cpp b/indra/llrender/llfontgl.cpp index cf0a117567..8cd18c5fa1 100644 --- a/indra/llrender/llfontgl.cpp +++ b/indra/llrender/llfontgl.cpp @@ -40,10 +40,17 @@  #include "v4color.h"  #include "lltexture.h"  #include "lldir.h" +#include "llstring.h"  // Third party library includes  #include <boost/tokenizer.hpp> +#if LL_WINDOWS +#include <Shlobj.h> +#include <Knownfolders.h> +#include <Objbase.h> +#endif // LL_WINDOWS +  const S32 BOLD_OFFSET = 1;  // static class members @@ -1063,33 +1070,33 @@ LLFontGL* LLFontGL::getFontDefault()  // static   std::string LLFontGL::getFontPathSystem()  { -	std::string system_path; - -	// Try to figure out where the system's font files are stored. -	char *system_root = NULL; -#if LL_WINDOWS -	system_root = getenv("SystemRoot");	/* Flawfinder: ignore */ -	if (!system_root) -	{ -		LL_WARNS() << "SystemRoot not found, attempting to load fonts from default path." << LL_ENDL; -	} +#if LL_DARWIN +    // HACK for Mac OS X +    return "/System/Library/Fonts/"; + +#elif LL_WINDOWS +    auto system_root = LLStringUtil::getenv("SystemRoot"); +    if (! system_root.empty()) +    { +        std::string fontpath(gDirUtilp->add(system_root, "fonts") + gDirUtilp->getDirDelimiter()); +        LL_INFOS() << "from SystemRoot: " << fontpath << LL_ENDL; +        return fontpath; +    } + +    wchar_t *pwstr = NULL; +    HRESULT okay = SHGetKnownFolderPath(FOLDERID_Fonts, 0, NULL, &pwstr); +    if (SUCCEEDED(okay) && pwstr) +    { +        std::string fontpath(ll_convert_wide_to_string(pwstr)); +        // SHGetKnownFolderPath() contract requires us to free pwstr +        CoTaskMemFree(pwstr); +        LL_INFOS() << "from SHGetKnownFolderPath(): " << fontpath << LL_ENDL; +        return fontpath; +    }  #endif -	if (system_root) -	{ -		system_path = llformat("%s/fonts/", system_root); -	} -	else -	{ -#if LL_WINDOWS -		// HACK for windows 98/Me -		system_path = "/WINDOWS/FONTS/"; -#elif LL_DARWIN -		// HACK for Mac OS X -		system_path = "/System/Library/Fonts/"; -#endif -	} -	return system_path; +    LL_WARNS() << "Could not determine system fonts path" << LL_ENDL; +    return {};  } diff --git a/indra/llui/llnotificationslistener.cpp b/indra/llui/llnotificationslistener.cpp index b6a32a0e78..be26416cbb 100644 --- a/indra/llui/llnotificationslistener.cpp +++ b/indra/llui/llnotificationslistener.cpp @@ -90,9 +90,12 @@ void LLNotificationsListener::requestAdd(const LLSD& event_data) const  {  	if(event_data.has("reply"))  	{ +		LLSD payload(event_data["payload"]); +		// copy reqid, if provided, to link response with request +		payload["reqid"] = event_data["reqid"];  		mNotifications.add(event_data["name"],   						   event_data["substitutions"],  -						   event_data["payload"], +						   payload,  						   boost::bind(&LLNotificationsListener::NotificationResponder,   									   this,   									   event_data["reply"].asString(),  @@ -112,10 +115,12 @@ void LLNotificationsListener::NotificationResponder(const std::string& reply_pum  										const LLSD& notification,   										const LLSD& response) const  { -	LLSD reponse_event; -	reponse_event["notification"] = notification; -	reponse_event["response"] = response; -	LLEventPumps::getInstance()->obtain(reply_pump).post(reponse_event); +	LLSD response_event; +	response_event["notification"] = notification; +	response_event["response"] = response; +	// surface reqid at top level of response for request/response protocol +	response_event["reqid"] = notification["payload"]["reqid"]; +	LLEventPumps::getInstance()->obtain(reply_pump).post(response_event);  }  void LLNotificationsListener::listChannels(const LLSD& params) const diff --git a/indra/llvfs/lldir.cpp b/indra/llvfs/lldir.cpp index 2069888774..18836e54b0 100644 --- a/indra/llvfs/lldir.cpp +++ b/indra/llvfs/lldir.cpp @@ -42,6 +42,7 @@  #include "lldiriterator.h"  #include "stringize.h" +#include "llstring.h"  #include <boost/filesystem.hpp>  #include <boost/foreach.hpp>  #include <boost/range/begin.hpp> @@ -317,9 +318,9 @@ const std::string& LLDir::getChatLogsDir() const  void LLDir::setDumpDir( const std::string& path )  {      LLDir::sDumpDir = path; -    if (! sDumpDir.empty() && sDumpDir.rbegin() == mDirDelimiter.rbegin() ) +    if (LLStringUtil::endsWith(sDumpDir, mDirDelimiter))      { -        sDumpDir.erase(sDumpDir.size() -1); +        sDumpDir.erase(sDumpDir.size() - mDirDelimiter.size());      }  } diff --git a/indra/llvfs/lldir_linux.cpp b/indra/llvfs/lldir_linux.cpp index 2cd06b81f8..80ad05345a 100644 --- a/indra/llvfs/lldir_linux.cpp +++ b/indra/llvfs/lldir_linux.cpp @@ -29,6 +29,7 @@  #include "lldir_linux.h"  #include "llerror.h"  #include "llrand.h" +#include "llstring.h"  #include <sys/types.h>  #include <sys/stat.h>  #include <unistd.h> @@ -40,28 +41,24 @@ static std::string getCurrentUserHome(char* fallback)  {  	const uid_t uid = getuid();  	struct passwd *pw; -	char *result_cstr = fallback; -	 +  	pw = getpwuid(uid);  	if ((pw != NULL) && (pw->pw_dir != NULL))  	{ -		result_cstr = (char*) pw->pw_dir; +		return pw->pw_dir; +	} + +	LL_INFOS() << "Couldn't detect home directory from passwd - trying $HOME" << LL_ENDL; +	auto home_env = LLStringUtil::getoptenv("HOME"); +	if (home_env) +	{ +		return *home_env;  	}  	else  	{ -		LL_INFOS() << "Couldn't detect home directory from passwd - trying $HOME" << LL_ENDL; -		const char *const home_env = getenv("HOME");	/* Flawfinder: ignore */  -		if (home_env) -		{ -			result_cstr = (char*) home_env; -		} -		else -		{ -			LL_WARNS() << "Couldn't detect home directory!  Falling back to " << fallback << LL_ENDL; -		} +		LL_WARNS() << "Couldn't detect home directory!  Falling back to " << fallback << LL_ENDL; +		return fallback;  	} -	 -	return std::string(result_cstr);  } @@ -156,18 +153,18 @@ void LLDir_Linux::initAppDirs(const std::string &app_name,  	if (!app_read_only_data_dir.empty())  	{  		mAppRODataDir = app_read_only_data_dir; -		mSkinBaseDir = mAppRODataDir + mDirDelimiter + "skins"; +		mSkinBaseDir = add(mAppRODataDir, "skins");  	}  	mAppName = app_name;  	std::string upper_app_name(app_name);  	LLStringUtil::toUpper(upper_app_name); -	char* app_home_env = getenv((upper_app_name + "_USER_DIR").c_str());	/* Flawfinder: ignore */  +	auto app_home_env(LLStringUtil::getoptenv(upper_app_name + "_USER_DIR"));  	if (app_home_env)  	{  		// user has specified own userappdir i.e. $SECONDLIFE_USER_DIR -		mOSUserAppDir = app_home_env; +		mOSUserAppDir = *app_home_env;  	}  	else  	{ diff --git a/indra/llvfs/lldir_mac.cpp b/indra/llvfs/lldir_mac.cpp index 79c4362747..87dc1b9795 100644 --- a/indra/llvfs/lldir_mac.cpp +++ b/indra/llvfs/lldir_mac.cpp @@ -171,9 +171,9 @@ void LLDir_Mac::initAppDirs(const std::string &app_name,  	if (!app_read_only_data_dir.empty())  	{  		mAppRODataDir = app_read_only_data_dir; -		mSkinBaseDir = mAppRODataDir + mDirDelimiter + "skins"; +		mSkinBaseDir = add(mAppRODataDir, "skins");  	} -	mCAFile = getExpandedFilename(LL_PATH_EXECUTABLE, "../Resources", "ca-bundle.crt"); +	mCAFile = add(mAppRODataDir, "ca-bundle.crt");  }  std::string LLDir_Mac::getCurPath() diff --git a/indra/llvfs/lldir_solaris.cpp b/indra/llvfs/lldir_solaris.cpp index d3536a12ee..f18560ff20 100644 --- a/indra/llvfs/lldir_solaris.cpp +++ b/indra/llvfs/lldir_solaris.cpp @@ -29,6 +29,7 @@  #include "lldir_solaris.h"  #include "llerror.h"  #include "llrand.h" +#include "llstring.h"  #include <sys/types.h>  #include <sys/stat.h>  #include <unistd.h> @@ -41,30 +42,28 @@  static std::string getCurrentUserHome(char* fallback)  { +	// fwiw this exactly duplicates getCurrentUserHome() in lldir_linux.cpp... +	// we should either derive both from LLDir_Posix or just axe Solaris.  	const uid_t uid = getuid();  	struct passwd *pw; -	char *result_cstr = fallback; -	 +  	pw = getpwuid(uid);  	if ((pw != NULL) && (pw->pw_dir != NULL))  	{ -		result_cstr = (char*) pw->pw_dir; +		return pw->pw_dir; +	} + +	LL_INFOS() << "Couldn't detect home directory from passwd - trying $HOME" << LL_ENDL; +	auto home_env = LLStringUtil::getoptenv("HOME"); +	if (home_env) +	{ +		return *home_env;  	}  	else  	{ -		LL_INFOS() << "Couldn't detect home directory from passwd - trying $HOME" << LL_ENDL; -		const char *const home_env = getenv("HOME");	/* Flawfinder: ignore */  -		if (home_env) -		{ -			result_cstr = (char*) home_env; -		} -		else -		{ -			LL_WARNS() << "Couldn't detect home directory!  Falling back to " << fallback << LL_ENDL; -		} +		LL_WARNS() << "Couldn't detect home directory!  Falling back to " << fallback << LL_ENDL; +		return fallback;  	} -	 -	return std::string(result_cstr);  } @@ -135,27 +134,15 @@ LLDir_Solaris::LLDir_Solaris()  	//NOTE: Why force people to cd into the package directory?  	//      Look for SECONDLIFE env variable and use it, if set. -	char *dcf = getenv("SECONDLIFE"); -	if(dcf != NULL){ -		(void)strcpy(path, dcf); -		(void)strcat(path, "/bin");	//NOTE:  make sure we point at the bin -		mExecutableDir = strdup(path); +	auto SECONDLIFE(LLDirUtil::getoptenv("SECONDLIFE")); +	if(SECONDLIFE){ +		mExecutableDir = add(*SECONDLIFE, "bin"); //NOTE:  make sure we point at the bin  	}else{ -			// plunk a null at last '/' to get exec dir -		char *s = execpath + strlen(execpath) -1; -		while(*s != '/' && s != execpath){ -			--s; -		} -	 -		if(s != execpath){ -			*s = (char)NULL; -	 -			mExecutableDir = strdup(execpath); -			LL_INFOS() << "mExecutableDir = [" << mExecutableDir << "]" << LL_ENDL; -		} +		mExecutableDir = getDirName(execpath); +		LL_INFOS() << "mExecutableDir = [" << mExecutableDir << "]" << LL_ENDL;  	} -	 -	mLLPluginDir = mExecutableDir + mDirDelimiter + "llplugin"; + +	mLLPluginDir = add(mExecutableDir, "llplugin");  	// *TODO: don't use /tmp, use $HOME/.secondlife/tmp or something.  	mTempDir = "/tmp"; @@ -175,17 +162,18 @@ void LLDir_Solaris::initAppDirs(const std::string &app_name,  	if (!app_read_only_data_dir.empty())  	{  		mAppRODataDir = app_read_only_data_dir; +		mSkinBaseDir = add(mAppRODataDir, "skins");  	}  	mAppName = app_name;  	std::string upper_app_name(app_name);  	LLStringUtil::toUpper(upper_app_name); -	char* app_home_env = getenv((upper_app_name + "_USER_DIR").c_str());	/* Flawfinder: ignore */  +	auto app_home_env(LLStringUtil::getoptenv(upper_app_name + "_USER_DIR"));  	if (app_home_env)  	{  		// user has specified own userappdir i.e. $SECONDLIFE_USER_DIR -		mOSUserAppDir = app_home_env; +		mOSUserAppDir = *app_home_env;  	}  	else  	{ diff --git a/indra/llvfs/lldir_win32.cpp b/indra/llvfs/lldir_win32.cpp index 9836fa28f2..b3b3afb37e 100644 --- a/indra/llvfs/lldir_win32.cpp +++ b/indra/llvfs/lldir_win32.cpp @@ -30,7 +30,9 @@  #include "lldir_win32.h"  #include "llerror.h" -#include "llrand.h"		// for gLindenLabRandomNumber +#include "llstring.h" +#include "stringize.h" +#include "llfile.h"  #include <shlobj.h>  #include <fstream> @@ -43,15 +45,87 @@  #define PACKVERSION(major,minor) MAKELONG(minor,major)  DWORD GetDllVersion(LPCTSTR lpszDllName); +namespace +{ // anonymous +    enum class prst { INIT, OPEN, SKIP } state = prst::INIT; +    // This is called so early that we can't count on static objects being +    // properly constructed yet, so declare a pointer instead of an instance. +    std::ofstream* prelogf = nullptr; + +    void prelog(const std::string& message) +    { +        boost::optional<std::string> prelog_name; + +        switch (state) +        { +        case prst::INIT: +            // assume we failed, until we succeed +            state = prst::SKIP; + +            prelog_name = LLStringUtil::getoptenv("PRELOG"); +            if (! prelog_name) +                // no PRELOG variable set, carry on +                return; +            prelogf = new llofstream(*prelog_name, std::ios_base::app); +            if (! (prelogf && prelogf->is_open())) +                // can't complain to anybody; how? +                return; +            // got the log file open, cool! +            state = prst::OPEN; +            (*prelogf) << "========================================================================" +                       << std::endl; +            // fall through, don't break + +        case prst::OPEN: +            (*prelogf) << message << std::endl; +            break; + +        case prst::SKIP: +            // either PRELOG isn't set, or we failed to open that pathname +            break; +        } +    } +} // anonymous namespace + +#define PRELOG(expression) prelog(STRINGIZE(expression)) +  LLDir_Win32::LLDir_Win32()  {  	// set this first: used by append() and add() methods  	mDirDelimiter = "\\"; +	WCHAR w_str[MAX_PATH];  	// Application Data is where user settings go. We rely on $APPDATA being -	// correct; in fact the VMP makes a point of setting it properly, since -	// Windows itself botches the job for non-ASCII usernames (MAINT-8087). -	mOSUserDir = ll_safe_string(getenv("APPDATA")); +	// correct. +	auto APPDATA = LLStringUtil::getoptenv("APPDATA"); +	if (APPDATA) +	{ +		mOSUserDir = *APPDATA; +	} +	PRELOG("APPDATA='" << mOSUserDir << "'"); +	// On Windows, we could have received a plain-ASCII pathname in which +	// non-ASCII characters have been munged to '?', or the pathname could +	// have been badly encoded and decoded such that we now have garbage +	// instead of a valid path. Check that mOSUserDir actually exists. +	if (mOSUserDir.empty() || ! fileExists(mOSUserDir)) +	{ +		PRELOG("APPDATA does not exist"); +		//HRESULT okay = SHGetFolderPath(NULL, CSIDL_APPDATA, NULL, 0, w_str); +		wchar_t *pwstr = NULL; +		HRESULT okay = SHGetKnownFolderPath(FOLDERID_RoamingAppData, 0, NULL, &pwstr); +		PRELOG("SHGetKnownFolderPath(FOLDERID_RoamingAppData) returned " << okay); +		if (SUCCEEDED(okay) && pwstr) +		{ +			// But of course, only update mOSUserDir if SHGetKnownFolderPath() works. +			mOSUserDir = ll_convert_wide_to_string(pwstr); +			// Not only that: update our environment so that child processes +			// will see a reasonable value as well. +			_wputenv_s(L"APPDATA", pwstr); +			// SHGetKnownFolderPath() contract requires us to free pwstr +			CoTaskMemFree(pwstr); +			PRELOG("mOSUserDir='" << mOSUserDir << "'"); +		} +	}  	// We want cache files to go on the local disk, even if the  	// user is on a network with a "roaming profile". @@ -61,9 +135,34 @@ LLDir_Win32::LLDir_Win32()  	//  	// We used to store the cache in AppData\Roaming, and the installer  	// cleans up that version on upgrade.  JC -	mOSCacheDir = ll_safe_string(getenv("LOCALAPPDATA")); +	auto LOCALAPPDATA = LLStringUtil::getoptenv("LOCALAPPDATA"); +	if (LOCALAPPDATA) +	{ +		mOSCacheDir = *LOCALAPPDATA; +	} +	PRELOG("LOCALAPPDATA='" << mOSCacheDir << "'"); +	// Windows really does not deal well with pathnames containing non-ASCII +	// characters. See above remarks about APPDATA. +	if (mOSCacheDir.empty() || ! fileExists(mOSCacheDir)) +	{ +		PRELOG("LOCALAPPDATA does not exist"); +		//HRESULT okay = SHGetFolderPath(NULL, CSIDL_LOCAL_APPDATA, NULL, 0, w_str); +		wchar_t *pwstr = NULL; +		HRESULT okay = SHGetKnownFolderPath(FOLDERID_LocalAppData, 0, NULL, &pwstr); +		PRELOG("SHGetKnownFolderPath(FOLDERID_LocalAppData) returned " << okay); +		if (SUCCEEDED(okay) && pwstr) +		{ +			// But of course, only update mOSCacheDir if SHGetKnownFolderPath() works. +			mOSCacheDir = ll_convert_wide_to_string(pwstr); +			// Update our environment so that child processes will see a +			// reasonable value as well. +			_wputenv_s(L"LOCALAPPDATA", pwstr); +			// SHGetKnownFolderPath() contract requires us to free pwstr +			CoTaskMemFree(pwstr); +			PRELOG("mOSCacheDir='" << mOSCacheDir << "'"); +		} +	} -	WCHAR w_str[MAX_PATH];  	if (GetTempPath(MAX_PATH, w_str))  	{  		if (wcslen(w_str))	/* Flawfinder: ignore */  diff --git a/indra/llwindow/llwindowmacosx.cpp b/indra/llwindow/llwindowmacosx.cpp index 843294c239..d4afbb15df 100644 --- a/indra/llwindow/llwindowmacosx.cpp +++ b/indra/llwindow/llwindowmacosx.cpp @@ -1429,12 +1429,10 @@ static CursorRef gCursors[UI_CURSOR_COUNT];  static void initPixmapCursor(int cursorid, int hotspotX, int hotspotY)  {  	// cursors are in <Application Bundle>/Contents/Resources/cursors_mac/UI_CURSOR_FOO.tif -	std::string fullpath = gDirUtilp->getAppRODataDir(); -	fullpath += gDirUtilp->getDirDelimiter(); -	fullpath += "cursors_mac"; -	fullpath += gDirUtilp->getDirDelimiter(); -	fullpath += cursorIDToName(cursorid); -	fullpath += ".tif"; +	std::string fullpath = gDirUtilp->add( +		gDirUtilp->getAppRODataDir(), +		"cursors_mac", +		cursorIDToName(cursorid) + std::string(".tif"));  	gCursors[cursorid] = createImageCursor(fullpath.c_str(), hotspotX, hotspotY);  } diff --git a/indra/llwindow/llwindowwin32.cpp b/indra/llwindow/llwindowwin32.cpp index 4ee4a5357c..504c1589b0 100644 --- a/indra/llwindow/llwindowwin32.cpp +++ b/indra/llwindow/llwindowwin32.cpp @@ -3275,8 +3275,10 @@ S32 OSMessageBoxWin32(const std::string& text, const std::string& caption, U32 t  		break;  	} -	// HACK! Doesn't properly handle wide strings! -	int retval_win = MessageBoxA(NULL, text.c_str(), caption.c_str(), uType); +	int retval_win = MessageBoxW(NULL, // HWND +								 ll_convert_string_to_wide(text).c_str(), +								 ll_convert_string_to_wide(caption).c_str(), +								 uType);  	S32 retval;  	switch(retval_win) diff --git a/indra/llxml/tests/llcontrol_test.cpp b/indra/llxml/tests/llcontrol_test.cpp index 2b691ffbb1..f7e43d6def 100644 --- a/indra/llxml/tests/llcontrol_test.cpp +++ b/indra/llxml/tests/llcontrol_test.cpp @@ -27,43 +27,31 @@  #include "linden_common.h"  #include "llsdserialize.h" +#include "llfile.h" +#include "stringize.h"  #include "../llcontrol.h"  #include "../test/lltut.h" +#include <memory> +#include <vector>  namespace tut  { -  	struct control_group  	{ -		LLControlGroup* mCG; +		std::unique_ptr<LLControlGroup> mCG;  		std::string mTestConfigDir;  		std::string mTestConfigFile; +		std::vector<std::string> mCleanups;  		static bool mListenerFired;  		control_group()  		{ -			mCG = new LLControlGroup("foo"); +			mCG.reset(new LLControlGroup("foo"));  			LLUUID random;  			random.generate();  			// generate temp dir -			std::ostringstream oStr; - -#ifdef LL_WINDOWS -			char* tmp_dir = getenv("TMP"); -			if(tmp_dir) -			{ -				oStr << tmp_dir << "/llcontrol-test-" << random << "/"; -			} -			else -			{ -				oStr << "c:/tmp/llcontrol-test-" << random << "/"; -			} -#else -			oStr << "/tmp/llcontrol-test-" << random << "/"; -#endif - -			mTestConfigDir = oStr.str(); +			mTestConfigDir = STRINGIZE(LLFile::tmpdir() << "llcontrol-test-" << random << "/");  			mTestConfigFile = mTestConfigDir + "settings.xml";  			LLFile::mkdir(mTestConfigDir);  			LLSD config; @@ -76,7 +64,12 @@ namespace tut  		~control_group()  		{  			//Remove test files -			delete mCG; +			for (auto filename : mCleanups) +			{ +				LLFile::remove(filename); +			} +			LLFile::remove(mTestConfigFile); +			LLFile::rmdir(mTestConfigDir);  		}  		void writeSettingsFile(const LLSD& config)  		{ @@ -118,6 +111,7 @@ namespace tut  		ensure_equals("value of changed setting", mCG->getU32("TestSetting"), 13);  		LLControlGroup test_cg("foo2");  		std::string temp_test_file = (mTestConfigDir + "setting_llsd_temp.xml"); +		mCleanups.push_back(temp_test_file);  		mCG->saveToFile(temp_test_file.c_str(), TRUE);  		results = test_cg.loadFromFile(temp_test_file.c_str());  		ensure("number of changed settings loaded", (results == 1)); @@ -139,6 +133,7 @@ namespace tut  		ensure_equals("value of changed setting", mCG->getU32("TestSetting"), 13);  		LLControlGroup test_cg("foo3");  		std::string temp_test_file = (mTestConfigDir + "setting_llsd_persist_temp.xml"); +		mCleanups.push_back(temp_test_file);  		mCG->saveToFile(temp_test_file.c_str(), TRUE);  		results = test_cg.loadFromFile(temp_test_file.c_str());  		//If we haven't changed any settings, then we shouldn't have any settings to load @@ -153,7 +148,7 @@ namespace tut  		ensure("number of settings", (results == 1));  		mCG->getControl("TestSetting")->getSignal()->connect(boost::bind(&this->handleListenerTest));  		mCG->setU32("TestSetting", 13); -		ensure("listener fired on changed setting", mListenerFired);	    +		ensure("listener fired on changed setting", mListenerFired);  	}  } diff --git a/indra/media_plugins/base/CMakeLists.txt b/indra/media_plugins/base/CMakeLists.txt index 70c81d4023..7f2b82ffdd 100644 --- a/indra/media_plugins/base/CMakeLists.txt +++ b/indra/media_plugins/base/CMakeLists.txt @@ -48,5 +48,5 @@ set(media_plugin_base_HEADER_FILES  add_library(media_plugin_base      ${media_plugin_base_SOURCE_FILES} -) +    ) diff --git a/indra/media_plugins/cef/CMakeLists.txt b/indra/media_plugins/cef/CMakeLists.txt index 5452fd9d1e..ce6278963d 100644 --- a/indra/media_plugins/cef/CMakeLists.txt +++ b/indra/media_plugins/cef/CMakeLists.txt @@ -81,7 +81,7 @@ list(APPEND media_plugin_cef_SOURCE_FILES ${media_plugin_cef_HEADER_FILES})  add_library(media_plugin_cef      SHARED      ${media_plugin_cef_SOURCE_FILES} -) +    )  #add_dependencies(media_plugin_cef  #  ${MEDIA_PLUGIN_BASE_LIBRARIES} diff --git a/indra/media_plugins/example/CMakeLists.txt b/indra/media_plugins/example/CMakeLists.txt index 6f5b28b8e9..eb067a7f6e 100644 --- a/indra/media_plugins/example/CMakeLists.txt +++ b/indra/media_plugins/example/CMakeLists.txt @@ -47,7 +47,7 @@ set(media_plugin_example_SOURCE_FILES  add_library(media_plugin_example      SHARED      ${media_plugin_example_SOURCE_FILES} -) +    )  target_link_libraries(media_plugin_example    ${LLPLUGIN_LIBRARIES} diff --git a/indra/media_plugins/gstreamer010/CMakeLists.txt b/indra/media_plugins/gstreamer010/CMakeLists.txt index 6d18814b1e..571eb57b24 100644 --- a/indra/media_plugins/gstreamer010/CMakeLists.txt +++ b/indra/media_plugins/gstreamer010/CMakeLists.txt @@ -56,7 +56,7 @@ set(media_plugin_gstreamer010_HEADER_FILES  add_library(media_plugin_gstreamer010      SHARED      ${media_plugin_gstreamer010_SOURCE_FILES} -) +    )  target_link_libraries(media_plugin_gstreamer010    ${LLPLUGIN_LIBRARIES} diff --git a/indra/media_plugins/libvlc/CMakeLists.txt b/indra/media_plugins/libvlc/CMakeLists.txt index d3e9243069..97392bbe08 100644 --- a/indra/media_plugins/libvlc/CMakeLists.txt +++ b/indra/media_plugins/libvlc/CMakeLists.txt @@ -48,7 +48,7 @@ set(media_plugin_libvlc_SOURCE_FILES  add_library(media_plugin_libvlc      SHARED      ${media_plugin_libvlc_SOURCE_FILES} -) +    )  target_link_libraries(media_plugin_libvlc    ${LLPLUGIN_LIBRARIES} diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt index 9d4b58d216..9933713911 100644 --- a/indra/newview/CMakeLists.txt +++ b/indra/newview/CMakeLists.txt @@ -3,7 +3,14 @@  project(viewer)  include(00-Common) +# DON'T move Linking.cmake to its place in the alphabetized list below: it +# sets variables on which the 3p .cmake files depend. +include(Linking) +  include(Boost) +if (BUGSPLAT_DB) +  include(bugsplat) +endif (BUGSPLAT_DB)  include(BuildPackagesInfo)  include(BuildVersion)  include(CMakeCopyIfDifferent) @@ -16,7 +23,6 @@ include(GLOD)  include(Hunspell)  include(JsonCpp)  include(LLAppearance) -include(LLBase)  include(LLAudio)  include(LLCA)  include(LLCharacter) @@ -37,14 +43,12 @@ include(LLUI)  include(LLVFS)  include(LLWindow)  include(LLXML) -include(Linking)  include(NDOF)  include(NVAPI)  include(OPENAL)  include(OpenGL)  include(OpenSSL)  include(PNG) -include(Requests)  include(TemplateCheck)  include(UI)  include(UnixInstall) @@ -93,6 +97,12 @@ include_directories(      ${CMAKE_CURRENT_SOURCE_DIR}      ) +if (BUGSPLAT_DB) +  include_directories( +    ${BUGSPLAT_INCLUDE_DIR} +    ) +endif (BUGSPLAT_DB) +  include_directories(SYSTEM      ${LLCOMMON_SYSTEM_INCLUDE_DIRS}      ${LLXML_SYSTEM_INCLUDE_DIRS} @@ -1357,6 +1367,14 @@ if (DARWIN)    # This should be compiled with the viewer.    LIST(APPEND viewer_SOURCE_FILES llappdelegate-objc.mm) +  set_source_files_properties( +    llappdelegate-objc.mm +    PROPERTIES +    COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" +    # BugsplatMac is a module, imported with @import. That language feature +    # demands these switches. +    COMPILE_FLAGS "-fmodules -fcxx-modules" +    )    find_library(AGL_LIBRARY AGL)    find_library(APPKIT_LIBRARY AppKit) @@ -1371,6 +1389,12 @@ if (DARWIN)      ${COREAUDIO_LIBRARY}      ) +  if (BUGSPLAT_DB) +    list(APPEND viewer_LIBRARIES +      ${BUGSPLAT_LIBRARIES} +      ) +  endif (BUGSPLAT_DB) +    # Add resource files to the project.    set(viewer_RESOURCE_FILES      secondlife.icns @@ -1396,6 +1420,11 @@ endif (DARWIN)  if (LINUX)      LIST(APPEND viewer_SOURCE_FILES llappviewerlinux.cpp) +    set_source_files_properties( +      llappviewerlinux.cpp +      PROPERTIES +      COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" +      )      LIST(APPEND viewer_SOURCE_FILES llappviewerlinux_api_dbus.cpp)      SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--as-needed") @@ -1412,6 +1441,11 @@ if (WINDOWS)           llappviewerwin32.cpp           llwindebug.cpp           ) +    set_source_files_properties( +      llappviewerwin32.cpp +      PROPERTIES +      COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}" +      )      list(APPEND viewer_HEADER_FILES           llappviewerwin32.h @@ -1548,7 +1582,6 @@ if (WINDOWS)          kernel32          odbc32          odbccp32 -        ole32          oleaut32          shell32          Vfw32 @@ -1694,6 +1727,11 @@ if (SDL_FOUND)      )  endif (SDL_FOUND) +if (BUGSPLAT_DB) +  set_property(TARGET ${VIEWER_BINARY_NAME} +    PROPERTY COMPILE_DEFINITIONS "LL_BUGSPLAT") +endif (BUGSPLAT_DB) +  # add package files  file(GLOB EVENT_HOST_SCRIPT_GLOB_LIST       ${CMAKE_CURRENT_SOURCE_DIR}/../viewer_components/*.py) @@ -1792,7 +1830,7 @@ if (WINDOWS)             ${SHARED_LIB_STAGING_DIR}/Debug/fmodexL.dll            )      endif (FMODEX) -     +      add_custom_command(        OUTPUT  ${CMAKE_CFG_INTDIR}/copy_touched.bat        COMMAND ${PYTHON_EXECUTABLE} @@ -1801,15 +1839,16 @@ if (WINDOWS)          --actions=copy          --arch=${ARCH}          --artwork=${ARTWORK_DIR} +        "--bugsplat=${BUGSPLAT_DB}"          --build=${CMAKE_CURRENT_BINARY_DIR}          --buildtype=${CMAKE_BUILD_TYPE} +        "--channel=${VIEWER_CHANNEL}"          --configuration=${CMAKE_CFG_INTDIR}          --dest=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}          --grid=${GRID} -        "--channel=${VIEWER_CHANNEL}" -        --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt          --source=${CMAKE_CURRENT_SOURCE_DIR}          --touch=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/copy_touched.bat +        --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt        DEPENDS          ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py          stage_third_party_libs @@ -1827,24 +1866,9 @@ if (WINDOWS)      add_dependencies(${VIEWER_BINARY_NAME}        SLPlugin -   windows-crash-logger +      windows-crash-logger      ) -    # sets the 'working directory' for debugging from visual studio. -    if (NOT UNATTENDED) -        add_custom_command( -            TARGET ${VIEWER_BINARY_NAME} POST_BUILD -            COMMAND ${CMAKE_SOURCE_DIR}/tools/vstool/vstool.exe -            ARGS -              --solution -              ${CMAKE_BINARY_DIR}/${CMAKE_PROJECT_NAME}.sln -              --workingdir -              ${VIEWER_BINARY_NAME} -              "${CMAKE_CURRENT_SOURCE_DIR}" -            COMMENT "Setting the ${VIEWER_BINARY_NAME} working directory for debugging." -            ) -    endif (NOT UNATTENDED) -      if (PACKAGE)        add_custom_command(          OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/event_host.tar.bz2 @@ -1867,15 +1891,16 @@ if (WINDOWS)            ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py            --arch=${ARCH}            --artwork=${ARTWORK_DIR} +          "--bugsplat=${BUGSPLAT_DB}"            --build=${CMAKE_CURRENT_BINARY_DIR}            --buildtype=${CMAKE_BUILD_TYPE}            "--channel=${VIEWER_CHANNEL}" -          --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt            --configuration=${CMAKE_CFG_INTDIR}            --dest=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}            --grid=${GRID}            --source=${CMAKE_CURRENT_SOURCE_DIR}            --touch=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/touched.bat +          --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt          DEPENDS              ${VIEWER_BINARY_NAME}              ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py @@ -1906,8 +1931,8 @@ else (WINDOWS)  endif (WINDOWS)  # *NOTE: - this list is very sensitive to ordering, test carefully on all -# platforms if you change the releative order of the entries here. -# In particular, cmake 2.6.4 (when buidling with linux/makefile generators) +# platforms if you change the relative order of the entries here. +# In particular, cmake 2.6.4 (when building with linux/makefile generators)  # appears to sometimes de-duplicate redundantly listed dependencies improperly.  # To work around this, higher level modules should be listed before the modules  # that they depend upon. -brad @@ -1982,6 +2007,12 @@ target_link_libraries(${VIEWER_BINARY_NAME}      ${LLAPPEARANCE_LIBRARIES}      ) +if (BUGSPLAT_DB) +  target_link_libraries(${VIEWER_BINARY_NAME} +    ${BUGSPLAT_LIBRARIES} +    ) +endif (BUGSPLAT_DB) +  set(ARTWORK_DIR ${CMAKE_CURRENT_SOURCE_DIR} CACHE PATH      "Path to artwork files.") @@ -2005,15 +2036,16 @@ if (LINUX)          ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py          --arch=${ARCH}          --artwork=${ARTWORK_DIR} +        "--bugsplat=${BUGSPLAT_DB}"          --build=${CMAKE_CURRENT_BINARY_DIR}          --buildtype=${CMAKE_BUILD_TYPE}          "--channel=${VIEWER_CHANNEL}" -        --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt          --configuration=${CMAKE_CFG_INTDIR}          --dest=${CMAKE_CURRENT_BINARY_DIR}/packaged          --grid=${GRID}          --source=${CMAKE_CURRENT_SOURCE_DIR}          --touch=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/.${product}.touched +        --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt        DEPENDS          ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py          ${COPY_INPUT_DEPENDENCIES} @@ -2027,17 +2059,18 @@ if (LINUX)      COMMAND ${PYTHON_EXECUTABLE}      ARGS        ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py -      --arch=${ARCH}        --actions=copy +      --arch=${ARCH}        --artwork=${ARTWORK_DIR} +      "--bugsplat=${BUGSPLAT_DB}"        --build=${CMAKE_CURRENT_BINARY_DIR}        --buildtype=${CMAKE_BUILD_TYPE} +      "--channel=${VIEWER_CHANNEL}"        --configuration=${CMAKE_CFG_INTDIR}        --dest=${CMAKE_CURRENT_BINARY_DIR}/packaged        --grid=${GRID} -      "--channel=${VIEWER_CHANNEL}" -      --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt        --source=${CMAKE_CURRENT_SOURCE_DIR} +      --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt      DEPENDS        ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py        ${COPY_INPUT_DEPENDENCIES} @@ -2055,37 +2088,46 @@ if (LINUX)  endif (LINUX)  if (DARWIN) -  # These all get set with PROPERTIES -  set(product "Second Life") -  # this is the setting for the Python wrapper, see SL-322 and WRAPPER line in Info-SecondLife.plist -  if (PACKAGE) -      set(MACOSX_WRAPPER_EXECUTABLE_NAME "SL_Launcher") -  else (PACKAGE) -      # force the name of the actual executable to allow running it within Xcode for debugging -      set(MACOSX_WRAPPER_EXECUTABLE_NAME "../Resources/Second Life Viewer.app/Contents/MacOS/Second Life") -  endif (PACKAGE) -  set(MACOSX_BUNDLE_INFO_STRING "Second Life Viewer") +  # These all get set with PROPERTIES. It's not that the property names are +  # magically known to CMake -- it's that these names are referenced in the +  # Info-SecondLife.plist file in the configure_file() directive below. +  set(product "${VIEWER_CHANNEL}") +  set(MACOSX_EXECUTABLE_NAME "${VIEWER_CHANNEL}") +  set(MACOSX_BUNDLE_INFO_STRING "${VIEWER_CHANNEL}")    set(MACOSX_BUNDLE_ICON_FILE "secondlife.icns")    set(MACOSX_BUNDLE_GUI_IDENTIFIER "com.secondlife.indra.viewer")    set(MACOSX_BUNDLE_LONG_VERSION_STRING "${VIEWER_CHANNEL} ${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}")    set(MACOSX_BUNDLE_BUNDLE_NAME "SecondLife") -  set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${VIEWER_SHORT_VERSION}") +  set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${VIEWER_SHORT_VERSION}.${VIEWER_VERSION_REVISION}")    set(MACOSX_BUNDLE_BUNDLE_VERSION "${VIEWER_SHORT_VERSION}${VIEWER_MACOSX_PHASE}${VIEWER_REVISION}")    set(MACOSX_BUNDLE_COPYRIGHT "Copyright © Linden Research, Inc. 2007")    set(MACOSX_BUNDLE_NSMAIN_NIB_FILE "SecondLife.nib")    set(MACOSX_BUNDLE_NSPRINCIPAL_CLASS "NSApplication") + +  # https://blog.kitware.com/upcoming-in-cmake-2-8-12-osx-rpath-support/ +  set(CMAKE_MACOSX_RPATH 1)    set_target_properties(      ${VIEWER_BINARY_NAME}      PROPERTIES      OUTPUT_NAME "${product}" +    # From Contents/MacOS/SecondLife, look in Contents/Frameworks +    INSTALL_RPATH "@loader_path/../Frameworks" +    # SIGH, as of 2018-05-24 (cmake 3.11.1) the INSTALL_RPATH property simply +    # does not work. Try this: +    LINK_FLAGS "-rpath @loader_path/../Frameworks"      MACOSX_BUNDLE_INFO_PLIST      "${CMAKE_CURRENT_SOURCE_DIR}/Info-SecondLife.plist"      ) +  set(VIEWER_APP_BUNDLE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${product}.app") +  set(VIEWER_APP_EXE "${VIEWER_APP_BUNDLE}/Contents/MacOS/${product}") +  set(VIEWER_APP_DSYM "${VIEWER_APP_EXE}.dSYM") +  set(VIEWER_APP_XCARCHIVE "${VIEWER_APP_BUNDLE}/../${product}.xcarchive.zip") +    configure_file(       "${CMAKE_CURRENT_SOURCE_DIR}/Info-SecondLife.plist" -     "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${product}.app/Contents/Info.plist" +     "${VIEWER_APP_BUNDLE}/Contents/Info.plist"      )    add_custom_command( @@ -2096,15 +2138,16 @@ if (DARWIN)        --actions=copy        --arch=${ARCH}        --artwork=${ARTWORK_DIR} +      "--bugsplat=${BUGSPLAT_DB}"        --build=${CMAKE_CURRENT_BINARY_DIR}        --buildtype=${CMAKE_BUILD_TYPE} +      --bundleid=${MACOSX_BUNDLE_GUI_IDENTIFIER} +      "--channel=${VIEWER_CHANNEL}"        --configuration=${CMAKE_CFG_INTDIR} -      --dest=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${product}.app +      --dest=${VIEWER_APP_BUNDLE}        --grid=${GRID} -      "--channel=${VIEWER_CHANNEL}" -      --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt -      --bundleid=${MACOSX_BUNDLE_GUI_IDENTIFIER}        --source=${CMAKE_CURRENT_SOURCE_DIR} +      --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt      DEPENDS        ${VIEWER_BINARY_NAME}        ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py @@ -2129,15 +2172,16 @@ if (DARWIN)            ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py            --arch=${ARCH}            --artwork=${ARTWORK_DIR} +          "--bugsplat=${BUGSPLAT_DB}"            --build=${CMAKE_CURRENT_BINARY_DIR}            --buildtype=${CMAKE_BUILD_TYPE} +          "--channel=${VIEWER_CHANNEL}"            --configuration=${CMAKE_CFG_INTDIR} -          --dest=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${product}.app +          --dest=${VIEWER_APP_BUNDLE}            --grid=${GRID} -          "--channel=${VIEWER_CHANNEL}" -          --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt            --source=${CMAKE_CURRENT_SOURCE_DIR}            --touch=${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/.${product}.touched +          --versionfile=${CMAKE_CURRENT_BINARY_DIR}/viewer_version.txt            ${SIGNING_SETTING}          DEPENDS            ${CMAKE_CURRENT_SOURCE_DIR}/viewer_manifest.py @@ -2149,67 +2193,152 @@ if (INSTALL)    include(${CMAKE_CURRENT_SOURCE_DIR}/ViewerInstall.cmake)  endif (INSTALL) -if (PACKAGE) -  set(SYMBOL_SEARCH_DIRS "") -  # Note that the path to VIEWER_SYMBOL_FILE must match that in ../../build.sh -  if (WINDOWS) -    list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}") -    set(VIEWER_SYMBOL_FILE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/secondlife-symbols-windows-$ENV{AUTOBUILD_ADDRSIZE}.tar.bz2") -    # slplugin.exe failing symbols dump - need to debug, might have to do with updated version of google breakpad -    # set(VIEWER_EXE_GLOBS "${VIEWER_BINARY_NAME}${CMAKE_EXECUTABLE_SUFFIX} slplugin.exe") -    set(VIEWER_EXE_GLOBS "${VIEWER_BINARY_NAME}${CMAKE_EXECUTABLE_SUFFIX}") -    set(VIEWER_LIB_GLOB "*${CMAKE_SHARED_MODULE_SUFFIX}") -    set(VIEWER_COPY_MANIFEST copy_w_viewer_manifest) -  endif (WINDOWS) -  if (DARWIN) -    list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}") -    # *TODO: Generate these search dirs in the cmake files related to each binary. -    list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/llplugin/slplugin/${CMAKE_CFG_INTDIR}") -    list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/mac_crash_logger/${CMAKE_CFG_INTDIR}") -    list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/media_plugins/gstreamer010/${CMAKE_CFG_INTDIR}") -    set(VIEWER_SYMBOL_FILE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/secondlife-symbols-darwin-$ENV{AUTOBUILD_ADDRSIZE}.tar.bz2") -    set(VIEWER_EXE_GLOBS "'Second Life' SLPlugin mac-crash-logger") -    set(VIEWER_EXE_GLOBS "'Second Life' mac-crash-logger") -    set(VIEWER_LIB_GLOB "*.dylib") -  endif (DARWIN) -  if (LINUX) -    list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_CURRENT_BINARY_DIR}/packaged") -    set(VIEWER_SYMBOL_FILE "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/secondlife-symbols-linux-$ENV{AUTOBUILD_ADDRSIZE}.tar.bz2") -    set(VIEWER_EXE_GLOBS "do-not-directly-run-secondlife-bin SLPlugin") -    set(VIEWER_EXE_GLOBS "do-not-directly-run-secondlife-bin") -    set(VIEWER_LIB_GLOB "*${CMAKE_SHARED_MODULE_SUFFIX}*") -    set(VIEWER_COPY_MANIFEST copy_l_viewer_manifest) -  endif (LINUX) - -  if(RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) -  if(CMAKE_CFG_INTDIR STREQUAL ".") -      set(LLBUILD_CONFIG ${CMAKE_BUILD_TYPE}) -  else(CMAKE_CFG_INTDIR STREQUAL ".") -      # set LLBUILD_CONFIG to be a shell variable evaluated at build time -      # reflecting the configuration we are currently building. -      set(LLBUILD_CONFIG ${CMAKE_CFG_INTDIR}) -  endif(CMAKE_CFG_INTDIR STREQUAL ".") -  add_custom_command(OUTPUT "${VIEWER_SYMBOL_FILE}" -    COMMAND "${PYTHON_EXECUTABLE}" -    ARGS -      "${CMAKE_CURRENT_SOURCE_DIR}/generate_breakpad_symbols.py" -      "${LLBUILD_CONFIG}" -      "${SYMBOL_SEARCH_DIRS}" -      "${VIEWER_EXE_GLOBS}" -      "${VIEWER_LIB_GLOB}" -      "${AUTOBUILD_INSTALL_DIR}/bin/dump_syms" -      "${VIEWER_SYMBOL_FILE}" -    DEPENDS generate_breakpad_symbols.py -        VERBATIM) - -  add_custom_target(generate_breakpad_symbols DEPENDS "${VIEWER_SYMBOL_FILE}" "${VIEWER_BINARY_NAME}" "${VIEWER_COPY_MANIFEST}") -  add_dependencies(generate_breakpad_symbols "${VIEWER_BINARY_NAME}") -  if (WINDOWS OR LINUX) -    add_dependencies(generate_breakpad_symbols "${VIEWER_COPY_MANIFEST}") -  endif (WINDOWS OR LINUX) -  add_dependencies(llpackage generate_breakpad_symbols) -  endif(RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) -endif (PACKAGE) +# Note that the conventional VIEWER_SYMBOL_FILE is set by ../../build.sh +if (PACKAGE AND (RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) AND VIEWER_SYMBOL_FILE) +  if (NOT BUGSPLAT_DB) +    # Breakpad symbol-file generation +    set(SYMBOL_SEARCH_DIRS "") +    if (WINDOWS) +      list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}") +      # slplugin.exe failing symbols dump - need to debug, might have to do with updated version of google breakpad +      # set(VIEWER_EXE_GLOBS "${VIEWER_BINARY_NAME}${CMAKE_EXECUTABLE_SUFFIX} slplugin.exe") +      set(VIEWER_EXE_GLOBS "${VIEWER_BINARY_NAME}${CMAKE_EXECUTABLE_SUFFIX}") +      set(VIEWER_LIB_GLOB "*${CMAKE_SHARED_MODULE_SUFFIX}") +      set(VIEWER_COPY_MANIFEST copy_w_viewer_manifest) +    endif (WINDOWS) +    if (DARWIN) +      list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}") +      # *TODO: Generate these search dirs in the cmake files related to each binary. +      list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/llplugin/slplugin/${CMAKE_CFG_INTDIR}") +      list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/mac_crash_logger/${CMAKE_CFG_INTDIR}") +      list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_BINARY_DIR}/media_plugins/gstreamer010/${CMAKE_CFG_INTDIR}") +      set(VIEWER_EXE_GLOBS "'${product}' SLPlugin mac-crash-logger") +      set(VIEWER_EXE_GLOBS "'${product}' mac-crash-logger") +      set(VIEWER_LIB_GLOB "*.dylib") +    endif (DARWIN) +    if (LINUX) +      list(APPEND SYMBOL_SEARCH_DIRS "${CMAKE_CURRENT_BINARY_DIR}/packaged") +      set(VIEWER_EXE_GLOBS "do-not-directly-run-secondlife-bin SLPlugin") +      set(VIEWER_EXE_GLOBS "do-not-directly-run-secondlife-bin") +      set(VIEWER_LIB_GLOB "*${CMAKE_SHARED_MODULE_SUFFIX}*") +      set(VIEWER_COPY_MANIFEST copy_l_viewer_manifest) +    endif (LINUX) + +    if(CMAKE_CFG_INTDIR STREQUAL ".") +        set(LLBUILD_CONFIG ${CMAKE_BUILD_TYPE}) +    else(CMAKE_CFG_INTDIR STREQUAL ".") +        # set LLBUILD_CONFIG to be a shell variable evaluated at build time +        # reflecting the configuration we are currently building. +        set(LLBUILD_CONFIG ${CMAKE_CFG_INTDIR}) +    endif(CMAKE_CFG_INTDIR STREQUAL ".") +    add_custom_command(OUTPUT "${VIEWER_SYMBOL_FILE}" +      COMMAND "${PYTHON_EXECUTABLE}" +      ARGS +        "${CMAKE_CURRENT_SOURCE_DIR}/generate_breakpad_symbols.py" +        "${LLBUILD_CONFIG}" +        "${SYMBOL_SEARCH_DIRS}" +        "${VIEWER_EXE_GLOBS}" +        "${VIEWER_LIB_GLOB}" +        "${AUTOBUILD_INSTALL_DIR}/bin/dump_syms" +        "${VIEWER_SYMBOL_FILE}" +      DEPENDS generate_breakpad_symbols.py +          VERBATIM) + +    add_custom_target(generate_symbols DEPENDS "${VIEWER_SYMBOL_FILE}" ${VIEWER_BINARY_NAME} "${VIEWER_COPY_MANIFEST}") +    add_dependencies(generate_symbols ${VIEWER_BINARY_NAME}) +    if (WINDOWS OR LINUX) +      add_dependencies(generate_symbols "${VIEWER_COPY_MANIFEST}") +    endif (WINDOWS OR LINUX) + +  else (NOT BUGSPLAT_DB) +    # BugSplat symbol-file generation +    if (WINDOWS) +      # Just pack up a tarball containing only the .pdb file for the +      # executable. Because we intend to use cygwin tar, we must render +      # VIEWER_SYMBOL_FILE in cygwin path syntax. +      execute_process(COMMAND "cygpath" "-u" "${VIEWER_SYMBOL_FILE}" +        OUTPUT_VARIABLE VIEWER_SYMBOL_FILE_CYGWIN +        OUTPUT_STRIP_TRAILING_WHITESPACE) +      execute_process(COMMAND "cygpath" "-u" "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}" +        OUTPUT_VARIABLE PARENT_DIRECTORY_CYGWIN +        OUTPUT_STRIP_TRAILING_WHITESPACE) +      add_custom_command(OUTPUT "${VIEWER_SYMBOL_FILE}" +        # Use of 'tar ...j' here assumes VIEWER_SYMBOL_FILE endswith .tar.bz2; +        # testing a string suffix is painful enough in CMake language that +        # we'll continue assuming it until forced to generalize. +        COMMAND "tar" +        ARGS +          "cjf" +          "${VIEWER_SYMBOL_FILE_CYGWIN}" +          "-C" +          "${PARENT_DIRECTORY_CYGWIN}" +          "secondlife-bin.pdb" +        DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/secondlife-bin.pdb" +        COMMENT "Packing viewer PDB into ${VIEWER_SYMBOL_FILE_CYGWIN}" +        ) +      add_custom_target(generate_symbols DEPENDS "${VIEWER_SYMBOL_FILE}" ${VIEWER_BINARY_NAME}) +      add_dependencies(generate_symbols ${VIEWER_BINARY_NAME}) +    endif (WINDOWS) +    if (DARWIN) +      # Have to run dsymutil first, then pack up the resulting .dSYM directory +      add_custom_command(OUTPUT "${VIEWER_APP_DSYM}" +        COMMAND "dsymutil" +        ARGS +          ${VIEWER_APP_EXE} +        COMMENT "Generating ${VIEWER_APP_DSYM}" +        ) +      add_custom_target(dsym_generate DEPENDS "${VIEWER_APP_DSYM}") +      add_dependencies(dsym_generate ${VIEWER_BINARY_NAME}) +      add_custom_command(OUTPUT "${VIEWER_SYMBOL_FILE}" +        # See above comments about "tar ...j" +        COMMAND "tar" +        ARGS +          "cjf" +          "${VIEWER_SYMBOL_FILE}" +          "-C" +          "${VIEWER_APP_DSYM}/.." +          "${product}.dSYM" +        DEPENDS "${VIEWER_APP_DSYM}" +        COMMENT "Packing dSYM into ${VIEWER_SYMBOL_FILE}" +        ) +      add_custom_target(dsym_tarball DEPENDS "${VIEWER_SYMBOL_FILE}") +      add_dependencies(dsym_tarball dsym_generate) +      add_custom_command(OUTPUT "${VIEWER_APP_XCARCHIVE}" +        COMMAND "zip" +        ARGS +          "-r" +          "${VIEWER_APP_XCARCHIVE}" +          "." +        WORKING_DIRECTORY "${VIEWER_APP_DSYM}/.." +        DEPENDS "${VIEWER_APP_DSYM}" +        COMMENT "Generating xcarchive.zip for upload to BugSplat" +        ) +      add_custom_target(dsym_xcarchive DEPENDS "${VIEWER_APP_XCARCHIVE}") +      add_dependencies(dsym_xcarchive dsym_generate) +      # Have to create a stamp file, and depend on it, to force CMake to run +      # the cleanup step. +      add_custom_command(OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/dsym.stamp" +        COMMAND rm -rf "${VIEWER_APP_DSYM}" +        COMMAND touch "${CMAKE_CURRENT_BINARY_DIR}/dsym.stamp" +        DEPENDS "${VIEWER_SYMBOL_FILE}" "${VIEWER_APP_XCARCHIVE}" +        COMMENT "Cleaning up dSYM" +        ) +      add_custom_target(generate_symbols DEPENDS +        "${VIEWER_APP_DSYM}" +        "${VIEWER_SYMBOL_FILE}" +        "${VIEWER_APP_XCARCHIVE}" +        "${CMAKE_CURRENT_BINARY_DIR}/dsym.stamp" +        ) +      add_dependencies(generate_symbols dsym_tarball dsym_xcarchive) +    endif (DARWIN) +    if (LINUX) +      # TBD +    endif (LINUX) +  endif (NOT BUGSPLAT_DB) + +  # for both BUGSPLAT_DB and Breakpad +  add_dependencies(llpackage generate_symbols) +endif ()  if (LL_TESTS)    # To add a viewer unit test, just add the test .cpp file below diff --git a/indra/newview/Info-SecondLife.plist b/indra/newview/Info-SecondLife.plist index af4cf26ac6..cfe9d991c5 100644 --- a/indra/newview/Info-SecondLife.plist +++ b/indra/newview/Info-SecondLife.plist @@ -5,7 +5,7 @@  	<key>CFBundleDevelopmentRegion</key>  	<string>English</string>  	<key>CFBundleExecutable</key> -	<string>${MACOSX_WRAPPER_EXECUTABLE_NAME}</string> +	<string>${MACOSX_EXECUTABLE_NAME}</string>  	<key>CFBundleGetInfoString</key>  	<string>${MACOSX_BUNDLE_INFO_STRING}</string>  	<key>CFBundleIconFile</key> @@ -21,7 +21,7 @@  	<key>CFBundlePackageType</key>  	<string>APPL</string>  	<key>CFBundleShortVersionString</key> -	<string>${MACOSX_BUNDLE_LONG_VERSION_STRING}</string> +	<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>  	<key>CFBundleSignature</key>  	<string>????</string>  	<key>CFBundleVersion</key> @@ -32,6 +32,8 @@  	<true/>  	<key>NSHumanReadableCopyright</key>  	<string>${MACOSX_BUNDLE_COPYRIGHT}</string> +	<key>NSMicrophoneUsageDescription</key> +	<string>For voice chat, you must grant permission for Second Life to use the microphone.</string>  	<key>CFBundleDocumentTypes</key>  	<array>  		<dict> diff --git a/indra/newview/VIEWER_VERSION.txt b/indra/newview/VIEWER_VERSION.txt index 9b9a244206..f3b5af39e4 100644 --- a/indra/newview/VIEWER_VERSION.txt +++ b/indra/newview/VIEWER_VERSION.txt @@ -1 +1 @@ -6.0.2 +6.1.1 diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index bb5ff19176..3e8a854df3 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -4415,6 +4415,17 @@        <key>Value</key>        <real>96.0</real>      </map> +    <key>ForceAddressSize</key> +    <map> +      <key>Comment</key> +      <string>Force Windows update to 32-bit or 64-bit viewer.</string> +      <key>Persist</key> +      <integer>1</integer> +      <key>Type</key> +      <string>U32</string> +      <key>Value</key> +      <integer>0</integer> +    </map>      <key>ForceAssetFail</key>      <map>        <key>Comment</key> @@ -13646,7 +13657,7 @@      <key>UpdaterServiceURL</key>      <map>        <key>Comment</key> -      <string>Default location for the updater service.</string> +      <string>Obsolete; no longer used.</string>        <key>Persist</key>        <integer>0</integer>        <key>Type</key> @@ -14127,17 +14138,6 @@        <key>Value</key>        <integer>1</integer>      </map> -    <key>VerboseLogs</key> -    <map> -      <key>Comment</key> -      <string>Display source file and line number for each log item for debugging purposes</string> -      <key>Persist</key> -      <integer>1</integer> -      <key>Type</key> -      <string>Boolean</string> -      <key>Value</key> -      <integer>0</integer> -    </map>      <key>VertexShaderEnable</key>      <map>        <key>Comment</key> @@ -16363,7 +16363,7 @@        <string>if true, disables running the GPU benchmark at startup        (default to class 1)</string>        <key>Persist</key> -      <integer>0</integer> +      <integer>1</integer>        <key>Type</key>        <string>Boolean</string>        <key>Value</key> diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi index 14c8dba39f..4f9a1b7804 100644 --- a/indra/newview/installers/windows/installer_template.nsi +++ b/indra/newview/installers/windows/installer_template.nsi @@ -18,8 +18,7 @@  ;;
  ;; Linden Research, Inc., 945 Battery Street, San Francisco, CA  94111  USA
  ;;
 -;; NSIS Unicode 2.46.5 or higher required
 -;; http://www.scratchpaper.com/
 +;; NSIS 3 or higher required for Unicode support
  ;;
  ;; Author: James Cook, TankMaster Finesmith, Don Kjer, Callum Prentice
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 @@ -27,6 +26,7 @@  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Compiler flags
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 +Unicode true
  SetOverwrite on				# Overwrite files
  SetCompress auto			# Compress if saves space
  SetCompressor /solid lzma	# Compress whole installer as one block
 @@ -46,28 +46,33 @@ RequestExecutionLevel admin	# For when we write to Program Files  ;; (these files are in the same place as the nsi template but the python script generates a new nsi file in the 
  ;; application directory so we have to add a path to these include files)
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 -!include "%%SOURCE%%\installers\windows\lang_da.nsi"
 -!include "%%SOURCE%%\installers\windows\lang_de.nsi"
 +;; Ansariel notes: "Under certain circumstances the installer will fall back
 +;; to the first defined (aka default) language version. So you want to include
 +;; en-us as first language file."
  !include "%%SOURCE%%\installers\windows\lang_en-us.nsi"
 +
 +# Danish and Polish no longer supported by the viewer itself
 +##!include "%%SOURCE%%\installers\windows\lang_da.nsi"
 +!include "%%SOURCE%%\installers\windows\lang_de.nsi"
  !include "%%SOURCE%%\installers\windows\lang_es.nsi"
  !include "%%SOURCE%%\installers\windows\lang_fr.nsi"
  !include "%%SOURCE%%\installers\windows\lang_ja.nsi"
  !include "%%SOURCE%%\installers\windows\lang_it.nsi"
 -!include "%%SOURCE%%\installers\windows\lang_pl.nsi"
 +##!include "%%SOURCE%%\installers\windows\lang_pl.nsi"
  !include "%%SOURCE%%\installers\windows\lang_pt-br.nsi"
  !include "%%SOURCE%%\installers\windows\lang_ru.nsi"
  !include "%%SOURCE%%\installers\windows\lang_tr.nsi"
  !include "%%SOURCE%%\installers\windows\lang_zh.nsi"
  # *TODO: Move these into the language files themselves
 -LangString LanguageCode ${LANG_DANISH}   "da"
 +##LangString LanguageCode ${LANG_DANISH}   "da"
  LangString LanguageCode ${LANG_GERMAN}   "de"
  LangString LanguageCode ${LANG_ENGLISH}  "en"
  LangString LanguageCode ${LANG_SPANISH}  "es"
  LangString LanguageCode ${LANG_FRENCH}   "fr"
  LangString LanguageCode ${LANG_JAPANESE} "ja"
  LangString LanguageCode ${LANG_ITALIAN}  "it"
 -LangString LanguageCode ${LANG_POLISH}   "pl"
 +##LangString LanguageCode ${LANG_POLISH}   "pl"
  LangString LanguageCode ${LANG_PORTUGUESEBR} "pt"
  LangString LanguageCode ${LANG_RUSSIAN}  "ru"
  LangString LanguageCode ${LANG_TURKISH}  "tr"
 @@ -80,9 +85,12 @@ Name ${INSTNAME}  SubCaption 0 $(LicenseSubTitleSetup)	# Override "license agreement" text
 +!define MUI_ICON   "%%SOURCE%%\installers\windows\install_icon.ico"
 +!define MUI_UNICON "%%SOURCE%%\installers\windows\uninstall_icon.ico"
 +
  BrandingText " "						# Bottom of window text
 -Icon          %%SOURCE%%\installers\windows\install_icon.ico
 -UninstallIcon %%SOURCE%%\installers\windows\uninstall_icon.ico
 +Icon          "${MUI_ICON}"
 +UninstallIcon "${MUI_UNICON}"
  WindowIcon on							# Show our icon in left corner
  BGGradient off							# No big background window
  CRCCheck on								# Make sure CRC is OK
 @@ -90,20 +98,50 @@ InstProgressFlags smooth colored		# New colored smooth look  SetOverwrite on							# Overwrite files by default
  AutoCloseWindow true					# After all files install, close window
 -# initial location of install (default when not already installed)
 -#   note: Now we defer looking for existing install until onInit when we
 -#   are able to engage the 32/64 registry function
 -InstallDir "%%PROGRAMFILES%%\${INSTNAME}"
 +# Registry key paths, ours and Microsoft's
 +!define LINDEN_KEY      "SOFTWARE\Linden Research, Inc."
 +!define INSTNAME_KEY    "${LINDEN_KEY}\${INSTNAME}"
 +!define MSCURRVER_KEY   "SOFTWARE\Microsoft\Windows\CurrentVersion"
 +!define MSNTCURRVER_KEY "SOFTWARE\Microsoft\Windows NT\CurrentVersion"
 +!define MSUNINSTALL_KEY "${MSCURRVER_KEY}\Uninstall\${INSTNAME}"
 +
 +# from http://nsis.sourceforge.net/Docs/MultiUser/Readme.html
 +### Highest level permitted for user: Admin for Admin, Standard for Standard
 +##!define MULTIUSER_EXECUTIONLEVEL Highest
 +!define MULTIUSER_EXECUTIONLEVEL Admin
 +!define MULTIUSER_MUI
 +### Look for /AllUsers or /CurrentUser switches
 +##!define MULTIUSER_INSTALLMODE_COMMANDLINE
 +# appended to $PROGRAMFILES, as affected by MULTIUSER_USE_PROGRAMFILES64
 +!define MULTIUSER_INSTALLMODE_INSTDIR "${INSTNAME}"
 +# expands to !define MULTIUSER_USE_PROGRAMFILES64 or nothing
 +%%PROGRAMFILES%%
 +# should make MultiUser.nsh initialization read existing INSTDIR from registry
 +## SL-10506: don't
 +##!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY "${INSTNAME_KEY}"
 +##!define MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME ""
 +# Don't set MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY and
 +# MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME to cause the installer to
 +# write $MultiUser.InstallMode to the registry, because when the user installs
 +# multiple viewers with the same channel (same ${INSTNAME}, hence same
 +# ${INSTNAME_KEY}), the registry entry is overwritten. Instead we'll write a
 +# little file into the install directory -- see .onInstSuccess and un.onInit.
 +!include MultiUser.nsh
 +!include MUI2.nsh
 +!define MUI_BGCOLOR FFFFFF
 +!insertmacro MUI_FUNCTION_GUIINIT
  UninstallText $(UninstallTextMsg)
  DirText $(DirectoryChooseTitle) $(DirectoryChooseSetup)
 -Page directory dirPre
 -Page instfiles
 +##!insertmacro MULTIUSER_PAGE_INSTALLMODE
 +!define MUI_PAGE_CUSTOMFUNCTION_PRE dirPre
 +!insertmacro MUI_PAGE_DIRECTORY
 +!insertmacro MUI_PAGE_INSTFILES
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  ;; Variables
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 -Var INSTPROG
 +Var INSTNAME
  Var INSTEXE
  Var VIEWER_EXE
  Var INSTSHORTCUT
 @@ -142,17 +180,21 @@ FunctionEnd  ;; entry to the language ID selector below
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  Function .onInit
 +!insertmacro MULTIUSER_INIT
  %%ENGAGEREGISTRY%%
 -# read the current location of the install for this version
 +# SL-10506: Setting MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_KEY and
 +# MULTIUSER_INSTALLMODE_INSTDIR_REGISTRY_VALUENAME should
 +# read the current location of the install for this version into INSTDIR.
 +# However, SL-10506 complains about the resulting behavior, so the logic below
 +# is adapted from before we introduced MultiUser.nsh.
 +
  # if $0 is empty, this is the first time for this viewer name
 -ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\\Linden Research, Inc.\\${INSTNAME}" ""
 +ReadRegStr $0 SHELL_CONTEXT "${INSTNAME_KEY}" ""
 -# viewer with this name not installed before
 -${If} $0 == ""
 -    # nothing to do here
 -${Else}
 +# viewer with this name was installed before
 +${If} $0 != ""
  	# use the value we got from registry as install location
      StrCpy $INSTDIR $0
  ${EndIf}
 @@ -181,7 +223,7 @@ Call CheckWindowsVersion					# Don't install On unsupported systems  lbl_configure_default_lang:
  # If we currently have a version of SL installed, default to the language of that install
  # Otherwise don't change $LANGUAGE and it will default to the OS UI language.
 -    ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\${INSTNAME}" "InstallerLanguage"
 +    ReadRegStr $0 SHELL_CONTEXT "${INSTNAME_KEY}" "InstallerLanguage"
      IfErrors +2 0	# If error skip the copy instruction 
  	StrCpy $LANGUAGE $0
 @@ -191,7 +233,6 @@ lbl_configure_default_lang:      Goto lbl_return
      StrCmp $SKIP_DIALOGS "true" lbl_return
 -lbl_build_menu:
  	Push ""
  # Use separate file so labels can be UTF-16 but we can still merge changes into this ASCII file. JC
      !include "%%SOURCE%%\installers\windows\language_menu.nsi"
 @@ -204,7 +245,7 @@ lbl_build_menu:      StrCpy $LANGUAGE $0
  # Save language in registry		
 -	WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\${INSTNAME}" "InstallerLanguage" $LANGUAGE
 +	WriteRegStr SHELL_CONTEXT "${INSTNAME_KEY}" "InstallerLanguage" $LANGUAGE
  lbl_return:
      Pop $0
      Return
 @@ -215,14 +256,32 @@ FunctionEnd  ;; Prep Uninstaller Section
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  Function un.onInit
 +    # Save $INSTDIR -- it appears to have the correct value before
 +    # MULTIUSER_UNINIT, but then gets munged by MULTIUSER_UNINIT?!
 +    Push $INSTDIR
 +    !insertmacro MULTIUSER_UNINIT
 +    Pop $INSTDIR
 +
 +    # Now read InstallMode.txt from $INSTDIR
 +    Push $0
 +    ClearErrors
 +    FileOpen $0 "$INSTDIR\InstallMode.txt" r
 +    IfErrors skipread
 +    FileRead $0 $MultiUser.InstallMode
 +    FileClose $0
 +skipread:
 +    Pop $0
  %%ENGAGEREGISTRY%%
  # Read language from registry and set for uninstaller. Key will be removed on successful uninstall
 -	ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\${INSTNAME}" "InstallerLanguage"
 +	ReadRegStr $0 SHELL_CONTEXT "${INSTNAME_KEY}" "InstallerLanguage"
      IfErrors lbl_end
  	StrCpy $LANGUAGE $0
  lbl_end:
 +
 +##  MessageBox MB_OK "After restoring:$\n$$INSTDIR = '$INSTDIR'$\n$$MultiUser.InstallMode = '$MultiUser.InstallMode'$\n$$LANGUAGE = '$LANGUAGE'"
 +
      Return
  FunctionEnd
 @@ -272,10 +331,10 @@ FunctionEnd  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  Section ""
 -SetShellVarContext all			# Install for all users (if you change this, change it in the uninstall as well)
 +# SetShellVarContext is set by MultiUser.nsh initialization.
  # Start with some default values.
 -StrCpy $INSTPROG "${INSTNAME}"
 +StrCpy $INSTNAME "${INSTNAME}"
  StrCpy $INSTEXE "${INSTEXE}"
  StrCpy $VIEWER_EXE "${VIEWER_EXE}"
  StrCpy $INSTSHORTCUT "${SHORTCUT}"
 @@ -299,7 +358,7 @@ StrCpy $SHORTCUT_LANG_PARAM "--set InstallLanguage $(LanguageCode)"  CreateDirectory	"$SMPROGRAMS\$INSTSHORTCUT"
  SetOutPath "$INSTDIR"
  CreateShortCut	"$SMPROGRAMS\$INSTSHORTCUT\$INSTSHORTCUT.lnk" \
 -				"$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM" "$INSTDIR\$VIEWER_EXE"
 +				"$INSTDIR\$VIEWER_EXE" "$SHORTCUT_LANG_PARAM" "$INSTDIR\$VIEWER_EXE"
  WriteINIStr		"$SMPROGRAMS\$INSTSHORTCUT\SL Create Account.url" \
 @@ -317,31 +376,31 @@ CreateShortCut	"$SMPROGRAMS\$INSTSHORTCUT\Uninstall $INSTSHORTCUT.lnk" \  # Other shortcuts
  SetOutPath "$INSTDIR"
  CreateShortCut "$DESKTOP\$INSTSHORTCUT.lnk" \
 -        "$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM" "$INSTDIR\$VIEWER_EXE"
 +        "$INSTDIR\$VIEWER_EXE" "$SHORTCUT_LANG_PARAM" "$INSTDIR\$VIEWER_EXE"
  CreateShortCut "$INSTDIR\$INSTSHORTCUT.lnk" \
 -        "$INSTDIR\$INSTEXE" "$SHORTCUT_LANG_PARAM" "$INSTDIR\$VIEWER_EXE"
 +        "$INSTDIR\$VIEWER_EXE" "$SHORTCUT_LANG_PARAM" "$INSTDIR\$VIEWER_EXE"
  CreateShortCut "$INSTDIR\Uninstall $INSTSHORTCUT.lnk" \
  				'"$INSTDIR\uninst.exe"' ''
  # Write registry
 -WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "" "$INSTDIR"
 -WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Version" "${VERSION_LONG}"
 -WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Shortcut" "$INSTSHORTCUT"
 -WriteRegStr HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG" "Exe" "$INSTEXE"
 -WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "Publisher" "Linden Research, Inc."
 -WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "URLInfoAbout" "http://secondlife.com/whatis/"
 -WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "URLUpdateInfo" "http://secondlife.com/support/downloads/"
 -WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "HelpLink" "https://support.secondlife.com/contact-support/"
 -WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "DisplayName" "$INSTPROG"
 -WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "UninstallString" '"$INSTDIR\uninst.exe"'
 -WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "DisplayVersion" "${VERSION_LONG}"
 -WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "EstimatedSize" "0x0001D500"		# ~117 MB
 +WriteRegStr SHELL_CONTEXT "${INSTNAME_KEY}" "" "$INSTDIR"
 +WriteRegStr SHELL_CONTEXT "${INSTNAME_KEY}" "Version" "${VERSION_LONG}"
 +WriteRegStr SHELL_CONTEXT "${INSTNAME_KEY}" "Shortcut" "$INSTSHORTCUT"
 +WriteRegStr SHELL_CONTEXT "${INSTNAME_KEY}" "Exe" "$VIEWER_EXE"
 +WriteRegStr SHELL_CONTEXT "${MSUNINSTALL_KEY}" "Publisher" "Linden Research, Inc."
 +WriteRegStr SHELL_CONTEXT "${MSUNINSTALL_KEY}" "URLInfoAbout" "http://secondlife.com/whatis/"
 +WriteRegStr SHELL_CONTEXT "${MSUNINSTALL_KEY}" "URLUpdateInfo" "http://secondlife.com/support/downloads/"
 +WriteRegStr SHELL_CONTEXT "${MSUNINSTALL_KEY}" "HelpLink" "https://support.secondlife.com/contact-support/"
 +WriteRegStr SHELL_CONTEXT "${MSUNINSTALL_KEY}" "DisplayName" "$INSTNAME"
 +WriteRegStr SHELL_CONTEXT "${MSUNINSTALL_KEY}" "UninstallString" '"$INSTDIR\uninst.exe"'
 +WriteRegStr SHELL_CONTEXT "${MSUNINSTALL_KEY}" "DisplayVersion" "${VERSION_LONG}"
 +WriteRegDWORD SHELL_CONTEXT "${MSUNINSTALL_KEY}" "EstimatedSize" "0x0001D500"		# ~117 MB
  # from FS:Ansariel
 -WriteRegStr HKEY_LOCAL_MACHINE "Software\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG" "DisplayIcon" '"$INSTDIR\$INSTEXE"'
 +WriteRegStr SHELL_CONTEXT "${MSUNINSTALL_KEY}" "DisplayIcon" '"$INSTDIR\$VIEWER_EXE"'
  # BUG-2707 Disable SEHOP for installed viewer.
 -WriteRegDWORD HKEY_LOCAL_MACHINE "Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\$INSTEXE" "DisableExceptionChainValidation" 1
 +WriteRegDWORD SHELL_CONTEXT "${MSNTCURRVER_KEY}\Image File Execution Options\$VIEWER_EXE" "DisableExceptionChainValidation" 1
  # Write URL registry info
  WriteRegStr HKEY_CLASSES_ROOT "${URLNAME}" "(default)" "URL:Second Life"
 @@ -358,9 +417,8 @@ WriteRegStr HKEY_CLASSES_ROOT "x-grid-location-info\DefaultIcon" "" '"$INSTDIR\$  # URL param must be last item passed to viewer, it ignores subsequent params to avoid parameter injection attacks.
  WriteRegExpandStr HKEY_CLASSES_ROOT "x-grid-location-info\shell\open\command" "" '"$INSTDIR\$VIEWER_EXE" -url "%1"'
 -# Only allow Launcher to be the icon
 -WriteRegStr HKEY_CLASSES_ROOT "Applications\$INSTEXE" "IsHostApp" ""
 -WriteRegStr HKEY_CLASSES_ROOT "Applications\${VIEWER_EXE}" "NoStartPage" ""
 +WriteRegStr HKEY_CLASSES_ROOT "Applications\$VIEWER_EXE" "IsHostApp" ""
 +##WriteRegStr HKEY_CLASSES_ROOT "Applications\${VIEWER_EXE}" "NoStartPage" ""
  # Write out uninstaller
  WriteUninstaller "$INSTDIR\uninst.exe"
 @@ -381,25 +439,32 @@ SectionEnd  Section Uninstall
  # Start with some default values.
 -StrCpy $INSTPROG "${INSTNAME}"
 +StrCpy $INSTNAME "${INSTNAME}"
  StrCpy $INSTEXE "${INSTEXE}"
 +StrCpy $VIEWER_EXE "${VIEWER_EXE}"
  StrCpy $INSTSHORTCUT "${SHORTCUT}"
 -# Make sure the user can install/uninstall
 -Call un.CheckIfAdministrator
 -
 -# Uninstall for all users (if you change this, change it in the install as well)
 -SetShellVarContext all			
 +# SetShellVarContext per the mode saved at install time in registry at
 +# MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_KEY
 +# MULTIUSER_INSTALLMODE_DEFAULT_REGISTRY_VALUENAME
 +# Couldn't get NSIS to expand $MultiUser.InstallMode into the function name at Call time
 +${If} $MultiUser.InstallMode == 'AllUsers'
 +##MessageBox MB_OK "Uninstalling for all users"
 +  Call un.MultiUser.InstallMode.AllUsers
 +${Else}
 +##MessageBox MB_OK "Uninstalling for current user"
 +  Call un.MultiUser.InstallMode.CurrentUser
 +${EndIf}
  # Make sure we're not running
  Call un.CloseSecondLife
  # Clean up registry keys and subkeys (these should all be !defines somewhere)
 -DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Linden Research, Inc.\$INSTPROG"
 -DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\$INSTPROG"
 +DeleteRegKey SHELL_CONTEXT "${INSTNAME_KEY}"
 +DeleteRegKey SHELL_CONTEXT "${MSCURRVER_KEY}\Uninstall\$INSTNAME"
  # BUG-2707 Remove entry that disabled SEHOP
 -DeleteRegKey HKEY_LOCAL_MACHINE "Software\Microsoft\Windows NT\CurrentVersion\Image File Execution Options\$INSTEXE"
 -DeleteRegKey HKEY_CLASSES_ROOT "Applications\$INSTEXE"
 +DeleteRegKey SHELL_CONTEXT "${MSNTCURRVER_KEY}\Image File Execution Options\$VIEWER_EXE"
 +##DeleteRegKey HKEY_CLASSES_ROOT "Applications\$INSTEXE"
  DeleteRegKey HKEY_CLASSES_ROOT "Applications\${VIEWER_EXE}"
  # Clean up shortcuts
 @@ -537,6 +602,7 @@ Function RemoveProgFilesOnInst  # Remove old SecondLife.exe to invalidate any old shortcuts to it that may be in non-standard locations. See MAINT-3575
  Delete "$INSTDIR\$INSTEXE"
 +Delete "$INSTDIR\$VIEWER_EXE"
  # Remove old shader files first so fallbacks will work. See DEV-5663
  RMDir /r "$INSTDIR\app_settings\shaders"
 @@ -570,10 +636,10 @@ Push $2    StrCpy $0 0	# Index number used to iterate via EnumRegKey
    LOOP:
 -    EnumRegKey $1 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $0
 +    EnumRegKey $1 SHELL_CONTEXT "${MSNTCURRVER_KEY}\ProfileList" $0
      StrCmp $1 "" DONE               # No more users
 -    ReadRegStr $2 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$1" "ProfileImagePath" 
 +    ReadRegStr $2 SHELL_CONTEXT "${MSNTCURRVER_KEY}\ProfileList\$1" "ProfileImagePath" 
      StrCmp $2 "" CONTINUE 0         # "ProfileImagePath" value is missing
  # Required since ProfileImagePath is of type REG_EXPAND_SZ
 @@ -603,7 +669,7 @@ Pop $0  # Delete files in ProgramData\Secondlife
  Push $0
 -  ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" "Common AppData"
 +  ReadRegStr $0 SHELL_CONTEXT "${MSCURRVER_KEY}\Explorer\Shell Folders" "Common AppData"
    StrCmp $0 "" +2
    RMDir /r "$0\SecondLife"
  Pop $0
 @@ -624,6 +690,9 @@ Function un.ProgramFiles  # This placeholder is replaced by the complete list of files to uninstall by viewer_manifest.py
  %%DELETE_FILES%%
 +# our InstallMode.txt
 +Delete "$INSTDIR\InstallMode.txt"
 +
  # Optional/obsolete files.  Delete won't fail if they don't exist.
  Delete "$INSTDIR\autorun.bat"
  Delete "$INSTDIR\dronesettings.ini"
 @@ -660,8 +729,8 @@ NOFOLDER:  MessageBox MB_YESNO $(DeleteRegistryKeysMB) IDYES DeleteKeys IDNO NoDelete
  DeleteKeys:
 -  DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Classes\x-grid-location-info"
 -  DeleteRegKey HKEY_LOCAL_MACHINE "SOFTWARE\Classes\secondlife"
 +  DeleteRegKey SHELL_CONTEXT "SOFTWARE\Classes\x-grid-location-info"
 +  DeleteRegKey SHELL_CONTEXT "SOFTWARE\Classes\secondlife"
    DeleteRegKey HKEY_CLASSES_ROOT "x-grid-location-info"
    DeleteRegKey HKEY_CLASSES_ROOT "secondlife"
 @@ -673,7 +742,13 @@ FunctionEnd  ;; After install completes, launch app
  ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
  Function .onInstSuccess
 -Call CheckWindowsServPack		# Warn if not on the latest SP before asking to launch.
 +        Push $0
 +        FileOpen $0 "$INSTDIR\InstallMode.txt" w
 +        # No newline -- this is for our use, not for users to read.
 +        FileWrite $0 "$MultiUser.InstallMode"
 +        FileClose $0
 +        Pop $0
 +
          Push $R0
          Push $0
          ;; MAINT-7812: Only write nsis.winstall file with /marker switch
 @@ -692,11 +767,24 @@ Call CheckWindowsServPack		# Warn if not on the latest SP before asking to launc          ClearErrors
          Pop $0
          Pop $R0
 -        Push $R0					# Option value, unused# 
 +
 +        Call CheckWindowsServPack		# Warn if not on the latest SP before asking to launch.
          StrCmp $SKIP_AUTORUN "true" +2;
 -# Assumes SetOutPath $INSTDIR
 -	Exec '"$WINDIR\explorer.exe" "$INSTDIR\$INSTSHORTCUT.lnk"'
 -        Pop $R0
 +        # Assumes SetOutPath $INSTDIR
 +        # Run INSTEXE (our updater), passing VIEWER_EXE plus the command-line
 +        # arguments built into our shortcuts. This gives the updater a chance
 +        # to verify that the viewer we just installed is appropriate for the
 +        # running system -- or, if not, to download and install a different
 +        # viewer. For instance, if a user running 32-bit Windows installs a
 +        # 64-bit viewer, it cannot run on this system. But since the updater
 +        # is a 32-bit executable even in the 64-bit viewer package, the
 +        # updater can detect the problem and adapt accordingly.
 +        # Once everything is in order, the updater will run the specified
 +        # viewer with the specified params.
 +        # Quote the updater executable and the viewer executable because each
 +        # must be a distinct command-line token, but DO NOT quote the language
 +        # string because it must decompose into separate command-line tokens.
 +        Exec '"$INSTDIR\$INSTEXE" precheck "$INSTDIR\$VIEWER_EXE" $SHORTCUT_LANG_PARAM'
  # 
  FunctionEnd
 @@ -733,10 +821,10 @@ FunctionEnd  ;    StrCpy $0 0	# Index number used to iterate via EnumRegKey
  ;
  ;  LOOP:
 -;    EnumRegKey $1 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList" $0
 +;    EnumRegKey $1 SHELL_CONTEXT "${MSNTCURRVER_KEY}\ProfileList" $0
  ;    StrCmp $1 "" DONE               # no more users
  ;
 -;    ReadRegStr $2 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows NT\CurrentVersion\ProfileList\$1" "ProfileImagePath"
 +;    ReadRegStr $2 SHELL_CONTEXT "${MSNTCURRVER_KEY}\ProfileList\$1" "ProfileImagePath"
  ;    StrCmp $2 "" CONTINUE 0         # "ProfileImagePath" value is missing
  ;
  ;# Required since ProfileImagePath is of type REG_EXPAND_SZ
 @@ -755,7 +843,7 @@ FunctionEnd  ;
  ;# Copy files in Documents and Settings\All Users\SecondLife
  ;Push $0
 -;    ReadRegStr $0 HKEY_LOCAL_MACHINE "SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders" "Common AppData"
 +;    ReadRegStr $0 SHELL_CONTEXT "${MSCURRVER_KEY}\Explorer\Shell Folders" "Common AppData"
  ;    StrCmp $0 "" +2
  ;    RMDir /r "$2\Application Data\SecondLife\"
  ;Pop $0
 diff --git a/indra/newview/installers/windows/lang_da.nsi b/indra/newview/installers/windows/lang_da.nsiBinary files differ index 83e1a3ea94..f462c82078 100644 --- a/indra/newview/installers/windows/lang_da.nsi +++ b/indra/newview/installers/windows/lang_da.nsi diff --git a/indra/newview/installers/windows/lang_de.nsi b/indra/newview/installers/windows/lang_de.nsiBinary files differ index 2a868acc89..8bb20476b3 100644..100755 --- a/indra/newview/installers/windows/lang_de.nsi +++ b/indra/newview/installers/windows/lang_de.nsi diff --git a/indra/newview/installers/windows/lang_en-us.nsi b/indra/newview/installers/windows/lang_en-us.nsiBinary files differ index 00aa47de69..fd4d340816 100644 --- a/indra/newview/installers/windows/lang_en-us.nsi +++ b/indra/newview/installers/windows/lang_en-us.nsi diff --git a/indra/newview/installers/windows/lang_es.nsi b/indra/newview/installers/windows/lang_es.nsiBinary files differ index 1ecf254ffb..8a81110069 100644..100755 --- a/indra/newview/installers/windows/lang_es.nsi +++ b/indra/newview/installers/windows/lang_es.nsi diff --git a/indra/newview/installers/windows/lang_fr.nsi b/indra/newview/installers/windows/lang_fr.nsiBinary files differ index bec5835bed..f038c0e419 100644..100755 --- a/indra/newview/installers/windows/lang_fr.nsi +++ b/indra/newview/installers/windows/lang_fr.nsi diff --git a/indra/newview/installers/windows/lang_it.nsi b/indra/newview/installers/windows/lang_it.nsiBinary files differ index 1d2e150525..bd16d8318f 100644..100755 --- a/indra/newview/installers/windows/lang_it.nsi +++ b/indra/newview/installers/windows/lang_it.nsi diff --git a/indra/newview/installers/windows/lang_ja.nsi b/indra/newview/installers/windows/lang_ja.nsiBinary files differ index 1bd6526670..71edde1992 100644..100755 --- a/indra/newview/installers/windows/lang_ja.nsi +++ b/indra/newview/installers/windows/lang_ja.nsi diff --git a/indra/newview/installers/windows/lang_pl.nsi b/indra/newview/installers/windows/lang_pl.nsiBinary files differ index a172f0cdeb..05977847b9 100644 --- a/indra/newview/installers/windows/lang_pl.nsi +++ b/indra/newview/installers/windows/lang_pl.nsi diff --git a/indra/newview/installers/windows/lang_pt-br.nsi b/indra/newview/installers/windows/lang_pt-br.nsiBinary files differ index 87032fec18..0e7cbeacda 100644..100755 --- a/indra/newview/installers/windows/lang_pt-br.nsi +++ b/indra/newview/installers/windows/lang_pt-br.nsi diff --git a/indra/newview/installers/windows/lang_ru.nsi b/indra/newview/installers/windows/lang_ru.nsiBinary files differ index 019c66123c..d55aacc971 100644..100755 --- a/indra/newview/installers/windows/lang_ru.nsi +++ b/indra/newview/installers/windows/lang_ru.nsi diff --git a/indra/newview/installers/windows/lang_tr.nsi b/indra/newview/installers/windows/lang_tr.nsiBinary files differ index 1c4e2c2f48..4746f84482 100644..100755 --- a/indra/newview/installers/windows/lang_tr.nsi +++ b/indra/newview/installers/windows/lang_tr.nsi diff --git a/indra/newview/installers/windows/lang_zh.nsi b/indra/newview/installers/windows/lang_zh.nsiBinary files differ index 355e01a333..397bd0ac81 100644..100755 --- a/indra/newview/installers/windows/lang_zh.nsi +++ b/indra/newview/installers/windows/lang_zh.nsi diff --git a/indra/newview/installers/windows/language_menu.nsi b/indra/newview/installers/windows/language_menu.nsiBinary files differ index 08ad42532f..2f426a0f47 100644 --- a/indra/newview/installers/windows/language_menu.nsi +++ b/indra/newview/installers/windows/language_menu.nsi diff --git a/indra/newview/llappdelegate-objc.mm b/indra/newview/llappdelegate-objc.mm index aebae4c434..1d55537427 100644 --- a/indra/newview/llappdelegate-objc.mm +++ b/indra/newview/llappdelegate-objc.mm @@ -25,7 +25,16 @@   */  #import "llappdelegate-objc.h" +#if defined(LL_BUGSPLAT) +#include <boost/filesystem.hpp> +#include <vector> +@import BugsplatMac; +// derived from BugsplatMac's BugsplatTester/AppDelegate.m +@interface LLAppDelegate () <BugsplatStartupManagerDelegate> +@end +#endif  #include "llwindowmacosx-objc.h" +#include "llappviewermacosx-for-objc.h"  #include <Carbon/Carbon.h> // Used for Text Input Services ("Safe" API - it's supported)  @implementation LLAppDelegate @@ -47,6 +56,25 @@  - (void) applicationDidFinishLaunching:(NSNotification *)notification  { +	// Call constructViewer() first so our logging subsystem is in place. This +	// risks missing crashes in the LLAppViewerMacOSX constructor, but for +	// present purposes it's more important to get the startup sequence +	// properly logged. +	// Someday I would like to modify the logging system so that calls before +	// it's initialized are cached in a std::ostringstream and then, once it's +	// initialized, "played back" into whatever handlers have been set up. +	constructViewer(); + +#if defined(LL_BUGSPLAT) +	// Engage BugsplatStartupManager *before* calling initViewer() to handle +	// any crashes during initialization. +	// https://www.bugsplat.com/docs/platforms/os-x#initialization +	[BugsplatStartupManager sharedManager].autoSubmitCrashReport = YES; +	[BugsplatStartupManager sharedManager].askUserDetails = NO; +	[BugsplatStartupManager sharedManager].delegate = self; +	[[BugsplatStartupManager sharedManager] start]; +#endif +  	frameTimer = nil;  	[self languageUpdated]; @@ -179,4 +207,138 @@      return true;  } +#if defined(LL_BUGSPLAT) + +- (NSString *)applicationLogForBugsplatStartupManager:(BugsplatStartupManager *)bugsplatStartupManager +{ +    CrashMetadata& meta(CrashMetadata_instance()); +    // As of BugsplatMac 1.0.6, userName and userEmail properties are now +    // exposed by the BugsplatStartupManager. Set them here, since the +    // defaultUserNameForBugsplatStartupManager and +    // defaultUserEmailForBugsplatStartupManager methods are called later, for +    // the *current* run, rather than for the previous crashed run whose crash +    // report we are about to send. +    infos("applicationLogForBugsplatStartupManager setting userName = '" + +          meta.agentFullname + '"'); +    bugsplatStartupManager.userName = +        [NSString stringWithCString:meta.agentFullname.c_str() +                           encoding:NSUTF8StringEncoding]; +    // Use the email field for OS version, just as we do on Windows, until +    // BugSplat provides more metadata fields. +    infos("applicationLogForBugsplatStartupManager setting userEmail = '" + +          meta.OSInfo + '"'); +    bugsplatStartupManager.userEmail = +        [NSString stringWithCString:meta.OSInfo.c_str() +                           encoding:NSUTF8StringEncoding]; +    // This strangely-named override method's return value contributes the +    // User Description metadata field. +    infos("applicationLogForBugsplatStartupManager -> '" + meta.fatalMessage + "'"); +    return [NSString stringWithCString:meta.fatalMessage.c_str() +                              encoding:NSUTF8StringEncoding]; +} + +- (NSString *)applicationKeyForBugsplatStartupManager:(BugsplatStartupManager *)bugsplatStartupManager signal:(NSString *)signal exceptionName:(NSString *)exceptionName exceptionReason:(NSString *)exceptionReason { +    // TODO: exceptionName, exceptionReason + +    // Windows sends location within region as well, but that's because +    // BugSplat for Windows intercepts crashes during the same run, and that +    // information can be queried once. On the Mac, any metadata we have is +    // written (and rewritten) to the static_debug_info.log file that we read +    // at the start of the next viewer run. It seems ridiculously expensive to +    // rewrite that file on every frame in which the avatar moves. +    std::string regionName(CrashMetadata_instance().regionName); +    infos("applicationKeyForBugsplatStartupManager -> '" + regionName + "'"); +    return [NSString stringWithCString:regionName.c_str() +                              encoding:NSUTF8StringEncoding]; +} + +- (NSString *)defaultUserNameForBugsplatStartupManager:(BugsplatStartupManager *)bugsplatStartupManager { +    std::string agentFullname(CrashMetadata_instance().agentFullname); +    infos("defaultUserNameForBugsplatStartupManager -> '" + agentFullname + "'"); +    return [NSString stringWithCString:agentFullname.c_str() +                              encoding:NSUTF8StringEncoding]; +} + +- (NSString *)defaultUserEmailForBugsplatStartupManager:(BugsplatStartupManager *)bugsplatStartupManager { +    // Use the email field for OS version, just as we do on Windows, until +    // BugSplat provides more metadata fields. +    std::string OSInfo(CrashMetadata_instance().OSInfo); +    infos("defaultUserEmailForBugsplatStartupManager -> '" + OSInfo + "'"); +    return [NSString stringWithCString:OSInfo.c_str() +                              encoding:NSUTF8StringEncoding]; +} + +- (void)bugsplatStartupManagerWillSendCrashReport:(BugsplatStartupManager *)bugsplatStartupManager +{ +    infos("bugsplatStartupManagerWillSendCrashReport"); +} + +struct AttachmentInfo +{ +    AttachmentInfo(const std::string& path, const std::string& type): +        pathname(path), +        basename(boost::filesystem::path(path).filename().string()), +        mimetype(type) +    {} + +    std::string pathname, basename, mimetype; +}; + +- (NSArray<BugsplatAttachment *> *)attachmentsForBugsplatStartupManager:(BugsplatStartupManager *)bugsplatStartupManager +{ +    const CrashMetadata& metadata(CrashMetadata_instance()); + +    // Since we must do very similar processing for each of several file +    // pathnames, start by collecting them into a vector so we can iterate +    // instead of spelling out the logic for each. +    std::vector<AttachmentInfo> info{ +        AttachmentInfo(metadata.logFilePathname,      "text/plain"), +        AttachmentInfo(metadata.userSettingsPathname, "text/xml"), +        AttachmentInfo(metadata.staticDebugPathname,  "text/xml") +    }; + +    // We "happen to know" that info[0].basename is "SecondLife.old" -- due to +    // the fact that BugsplatMac only notices a crash during the viewer run +    // following the crash. Replace .old with .log to reduce confusion. +    info[0].basename =  +        boost::filesystem::path(info[0].pathname).stem().string() + ".log"; + +    NSMutableArray *attachments = [[NSMutableArray alloc] init]; + +    // Iterate over each AttachmentInfo in info vector +    for (const AttachmentInfo& attach : info) +    { +        NSString *nspathname = [NSString stringWithCString:attach.pathname.c_str() +                                                  encoding:NSUTF8StringEncoding]; +        NSString *nsbasename = [NSString stringWithCString:attach.basename.c_str() +                                                  encoding:NSUTF8StringEncoding]; +        NSString *nsmimetype = [NSString stringWithCString:attach.mimetype.c_str() +                                                  encoding:NSUTF8StringEncoding]; +        NSData *nsdata = [NSData dataWithContentsOfFile:nspathname]; + +        BugsplatAttachment *attachment = +            [[BugsplatAttachment alloc] initWithFilename:nsbasename +                                          attachmentData:nsdata +                                             contentType:nsmimetype]; + +        [attachments addObject:attachment]; +        infos("attachmentsForBugsplatStartupManager attaching " + attach.pathname); +    } + +    return attachments; +} + +- (void)bugsplatStartupManagerDidFinishSendingCrashReport:(BugsplatStartupManager *)bugsplatStartupManager +{ +    infos("Sent crash report to BugSplat"); +} + +- (void)bugsplatStartupManager:(BugsplatStartupManager *)bugsplatStartupManager didFailWithError:(NSError *)error +{ +    // TODO: message string from NSError +    infos("Could not send crash report to BugSplat"); +} + +#endif // LL_BUGSPLAT +  @end diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp index cac951a3c5..07f85a2dd5 100644 --- a/indra/newview/llappviewer.cpp +++ b/indra/newview/llappviewer.cpp @@ -706,6 +706,22 @@ LLAppViewer::LLAppViewer()  	//  	LLLoginInstance::instance().setPlatformInfo(gPlatform, LLOSInfo::instance().getOSVersionString(), LLOSInfo::instance().getOSStringSimple()); + +	// Under some circumstances we want to read the static_debug_info.log file +	// from the previous viewer run between this constructor call and the +	// init() call, which will overwrite the static_debug_info.log file for +	// THIS run. So setDebugFileNames() early. +#if LL_BUGSPLAT +	// MAINT-8917: don't create a dump directory just for the +	// static_debug_info.log file +	std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_LOGS, ""); +#else // ! LL_BUGSPLAT +	// write Google Breakpad minidump files to a per-run dump directory to avoid multiple viewer issues. +	std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_DUMP, ""); +#endif // ! LL_BUGSPLAT +	mDumpPath = logdir; +	setMiniDumpDir(logdir); +	setDebugFileNames(logdir);  }  LLAppViewer::~LLAppViewer() @@ -782,13 +798,6 @@ bool LLAppViewer::init()  	initMaxHeapSize() ;  	LLCoros::instance().setStackSize(gSavedSettings.getS32("CoroutineStackSize")); -	// write Google Breakpad minidump files to a per-run dump directory to avoid multiple viewer issues. -	std::string logdir = gDirUtilp->getExpandedFilename(LL_PATH_DUMP, ""); -	mDumpPath = logdir; -	setMiniDumpDir(logdir); -	logdir += gDirUtilp->getDirDelimiter(); -    setDebugFileNames(logdir); -  	// Although initLoggingAndGetLastDuration() is the right place to mess with  	// setFatalFunction(), we can't query gSavedSettings until after @@ -877,11 +886,6 @@ bool LLAppViewer::init()  	mNumSessions++;  	gSavedSettings.setS32("NumSessions", mNumSessions); -	if (gSavedSettings.getBOOL("VerboseLogs")) -	{ -		LLError::setPrintLocation(true); -	} -  	// LLKeyboard relies on LLUI to know what some accelerator keys are called.  	LLKeyboard::setStringTranslatorFunc( LLTrans::getKeyboardString ); @@ -1084,26 +1088,6 @@ bool LLAppViewer::init()  		}  	} -// don't nag developers who need to run the executable directly -#if LL_RELEASE_FOR_DOWNLOAD -	// MAINT-8305: If we're processing a SLURL, skip the launcher check. -	if (gSavedSettings.getString("CmdLineLoginLocation").empty() && !beingDebugged()) -	{ -		const char* PARENT = getenv("PARENT"); -		if (! (PARENT && std::string(PARENT) == "SL_Launcher")) -		{ -			// Don't directly run this executable. Please run the launcher, which -			// will run the viewer itself. -			// Naturally we do not consider this bulletproof. The point is to -			// gently remind a user who *inadvertently* finds him/herself in this -			// situation to do things the Right Way. Anyone who intentionally -			// bypasses this mechanism needs no reminder that s/he's shooting -			// him/herself in the foot. -			LLNotificationsUtil::add("RunLauncher"); -		} -	} -#endif -  #if LL_WINDOWS  	if (gGLManager.mGLVersion < LLFeatureManager::getInstance()->getExpectedGLVersion())  	{ @@ -1151,6 +1135,34 @@ bool LLAppViewer::init()  	gGLActive = FALSE; +	LLProcess::Params updater; +	updater.desc = "updater process"; +	// Because it's the updater, it MUST persist beyond the lifespan of the +	// viewer itself. +	updater.autokill = false; +#if LL_WINDOWS +	updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "SLVersionChecker.exe"); +#elif LL_DARWIN +	// explicitly run the system Python interpreter on SLVersionChecker.py +	updater.executable = "python"; +	updater.args.add(gDirUtilp->add(gDirUtilp->getAppRODataDir(), "updater", "SLVersionChecker.py")); +#else +	updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "SLVersionChecker"); +#endif +	// add LEAP mode command-line argument to whichever of these we selected +	updater.args.add("leap"); +	// UpdaterServiceSettings +	updater.args.add(stringize(gSavedSettings.getU32("UpdaterServiceSetting"))); +	// channel +	updater.args.add(LLVersionInfo::getChannel()); +	// testok +	updater.args.add(stringize(gSavedSettings.getBOOL("UpdaterWillingToTest"))); +	// ForceAddressSize +	updater.args.add(stringize(gSavedSettings.getU32("ForceAddressSize"))); + +	// Run the updater. An exception from launching the updater should bother us. +	LLLeap::create(updater, true); +  	// Iterate over --leap command-line options. But this is a bit tricky: if  	// there's only one, it won't be an array at all.  	LLSD LeapCommand(gSavedSettings.getLLSD("LeapCommand")); @@ -1696,7 +1708,7 @@ bool LLAppViewer::cleanup()  	release_start_screen(); // just in case -	LLError::logToFixedBuffer(NULL); +	LLError::logToFixedBuffer(NULL); // stop the fixed buffer recorder  	LL_INFOS() << "Cleaning Up" << LL_ENDL; @@ -2175,6 +2187,12 @@ void errorCallback(const std::string &error_string)  	//Set the ErrorActivated global so we know to create a marker file  	gLLErrorActivated = true; +	gDebugInfo["FatalMessage"] = error_string; +	// We're not already crashing -- we simply *intend* to crash. Since we +	// haven't actually trashed anything yet, we can afford to write the whole +	// static info file. +	LLAppViewer::instance()->writeDebugInfo(); +  	LLError::crashAndLoop(error_string);  } @@ -3042,14 +3060,11 @@ void LLAppViewer::writeDebugInfo(bool isStatic)          ? getStaticDebugFile()          : getDynamicDebugFile() ); -	LL_INFOS() << "Opening debug file " << *debug_filename << LL_ENDL; -	llofstream out_file(debug_filename->c_str()); +    LL_INFOS() << "Writing debug file " << *debug_filename << LL_ENDL; +    llofstream out_file(debug_filename->c_str());      isStatic ?  LLSDSerialize::toPrettyXML(gDebugInfo, out_file)               :  LLSDSerialize::toPrettyXML(gDebugInfo["Dynamic"], out_file); - - -	out_file.close();  }  LLSD LLAppViewer::getViewerInfo() const diff --git a/indra/newview/llappviewermacosx-for-objc.h b/indra/newview/llappviewermacosx-for-objc.h new file mode 100644 index 0000000000..37e8a3917a --- /dev/null +++ b/indra/newview/llappviewermacosx-for-objc.h @@ -0,0 +1,53 @@ +/** + * @file   llappviewermacosx-for-objc.h + * @author Nat Goodspeed + * @date   2018-06-15 + * @brief  llappviewermacosx.h publishes the C++ API for + *         llappviewermacosx.cpp, just as + *         llappviewermacosx-objc.h publishes the Objective-C++ API for + *         llappviewermacosx-objc.mm. + * + *         This header is intended to publish for Objective-C++ consumers a + *         subset of the C++ API presented by llappviewermacosx.cpp. It's a + *         subset because, if an Objective-C++ consumer were to #include + *         the full llappviewermacosx.h, we would almost surely run into + *         trouble due to the discrepancy between Objective-C++'s BOOL versus + *         classic Microsoft/Linden BOOL. + *  + * $LicenseInfo:firstyear=2018&license=viewerlgpl$ + * Copyright (c) 2018, Linden Research, Inc. + * $/LicenseInfo$ + */ + +#if ! defined(LL_LLAPPVIEWERMACOSX_FOR_OBJC_H) +#define LL_LLAPPVIEWERMACOSX_FOR_OBJC_H + +#include <string> + +void constructViewer(); +bool initViewer(); +void handleUrl(const char* url_utf8); +bool pumpMainLoop(); +void handleQuit(); +void cleanupViewer(); +void infos(const std::string& message); + +// This struct is malleable; it only serves as a way to convey a number of +// fields from llappviewermacosx.cpp's CrashMetadata_instance() function to the +// consuming functions in llappdelegate-objc.mm. As long as both those sources +// are compiled with this same header, the content and order of CrashMetadata +// can change as needed. +struct CrashMetadata +{ +    std::string logFilePathname; +    std::string userSettingsPathname; +    std::string staticDebugPathname; +    std::string OSInfo; +    std::string agentFullname; +    std::string regionName; +    std::string fatalMessage; +}; + +CrashMetadata& CrashMetadata_instance(); + +#endif /* ! defined(LL_LLAPPVIEWERMACOSX_FOR_OBJC_H) */ diff --git a/indra/newview/llappviewermacosx.cpp b/indra/newview/llappviewermacosx.cpp index d472f8926b..81f04744f8 100644 --- a/indra/newview/llappviewermacosx.cpp +++ b/indra/newview/llappviewermacosx.cpp @@ -36,20 +36,25 @@  #include "llappviewermacosx-objc.h"  #include "llappviewermacosx.h" +#include "llappviewermacosx-for-objc.h"  #include "llwindowmacosx-objc.h"  #include "llcommandlineparser.h" +#include "llsdserialize.h"  #include "llviewernetwork.h"  #include "llviewercontrol.h"  #include "llmd5.h"  #include "llfloaterworldmap.h"  #include "llurldispatcher.h" +#include "llerrorcontrol.h" +#include "llvoavatarself.h"         // for gAgentAvatarp->getFullname()  #include <ApplicationServices/ApplicationServices.h>  #ifdef LL_CARBON_CRASH_HANDLER  #include <Carbon/Carbon.h>  #endif  #include <vector>  #include <exception> +#include <fstream>  #include "lldir.h"  #include <signal.h> @@ -81,7 +86,7 @@ static void exceptionTerminateHandler()  	gOldTerminateHandler(); // call old terminate() handler  } -bool initViewer() +void constructViewer()  {  	// Set the working dir to <bundle>/Contents/Resources  	if (chdir(gDirUtilp->getAppRODataDir().c_str()) == -1) @@ -97,18 +102,20 @@ bool initViewer()  	gOldTerminateHandler = std::set_terminate(exceptionTerminateHandler);  	gViewerAppPtr->setErrorHandler(LLAppViewer::handleViewerCrash); +} -	 +bool initViewer() +{  	bool ok = gViewerAppPtr->init();  	if(!ok)  	{  		LL_WARNS() << "Application init failed." << LL_ENDL;  	} -    else if (!gHandleSLURL.empty()) -    { -        dispatchUrl(gHandleSLURL); -        gHandleSLURL = ""; -    } +	else if (!gHandleSLURL.empty()) +	{ +		dispatchUrl(gHandleSLURL); +		gHandleSLURL = ""; +	}  	return ok;  } @@ -147,6 +154,73 @@ void cleanupViewer()  	gViewerAppPtr = NULL;  } +// The BugsplatMac API is structured as a number of different method +// overrides, each returning a different piece of metadata. But since we +// obtain such metadata by opening and parsing a file, it seems ridiculous to +// reopen and reparse it for every individual string desired. What we want is +// to open and parse the file once, retaining the data for subsequent +// requests. That's why this is an LLSingleton. +// Another approach would be to provide a function that simply returns +// CrashMetadata, storing the struct in LLAppDelegate, but nat doesn't know +// enough Objective-C++ to code that. We'd still have to detect which of the +// method overrides is called first so that the results are order-insensitive. +class CrashMetadataSingleton: public CrashMetadata, public LLSingleton<CrashMetadataSingleton> +{ +    LLSINGLETON(CrashMetadataSingleton); + +    // convenience method to log each metadata field retrieved by constructor +    std::string get_metadata(const LLSD& info, const LLSD::String& key) const +    { +        std::string data(info[key].asString()); +        LL_INFOS() << "  " << key << "='" << data << "'" << LL_ENDL; +        return data; +    } +}; + +// Populate the fields of our public base-class struct. +CrashMetadataSingleton::CrashMetadataSingleton() +{ +    // Note: we depend on being able to read the static_debug_info.log file +    // from the *previous* run before we overwrite it with the new one for +    // *this* run. LLAppViewer initialization must happen in the Right Order. +    staticDebugPathname = *gViewerAppPtr->getStaticDebugFile(); +    std::ifstream static_file(staticDebugPathname); +    LLSD info; +    if (! static_file.is_open()) +    { +        LL_INFOS() << "Can't open '" << staticDebugPathname +                   << "'; no metadata about previous run" << LL_ENDL; +    } +    else if (! LLSDSerialize::deserialize(info, static_file, LLSDSerialize::SIZE_UNLIMITED)) +    { +        LL_INFOS() << "Can't parse '" << staticDebugPathname +                   << "'; no metadata about previous run" << LL_ENDL; +    } +    else +    { +        LL_INFOS() << "Metadata from '" << staticDebugPathname << "':" << LL_ENDL; +        logFilePathname      = get_metadata(info, "SLLog"); +        userSettingsPathname = get_metadata(info, "SettingsFilename"); +        OSInfo               = get_metadata(info, "OSInfo"); +        agentFullname        = get_metadata(info, "LoginName"); +        // Translate underscores back to spaces +        LLStringUtil::replaceChar(agentFullname, '_', ' '); +        regionName           = get_metadata(info, "CurrentRegion"); +        fatalMessage         = get_metadata(info, "FatalMessage"); +    } +} + +// Avoid having to compile all of our LLSingleton machinery in Objective-C++. +CrashMetadata& CrashMetadata_instance() +{ +    return CrashMetadataSingleton::instance(); +} + +void infos(const std::string& message) +{ +    LL_INFOS() << message << LL_ENDL; +} +  int main( int argc, char **argv )   {  	// Store off the command line args for use later. diff --git a/indra/newview/llappviewerwin32.cpp b/indra/newview/llappviewerwin32.cpp index 3942613ee0..fff2653c98 100644 --- a/indra/newview/llappviewerwin32.cpp +++ b/indra/newview/llappviewerwin32.cpp @@ -66,8 +66,101 @@  #endif  #include "stringize.h" +#include "lldir.h" +#include "llerrorcontrol.h" +#include <fstream>  #include <exception> + +// Bugsplat (http://bugsplat.com) crash reporting tool +#ifdef LL_BUGSPLAT +#include "BugSplat.h" +#include "reader.h"                 // JsonCpp +#include "llagent.h"                // for agent location +#include "llviewerregion.h" +#include "llvoavatarself.h"         // for agent name + +namespace +{ +    // MiniDmpSender's constructor is defined to accept __wchar_t* instead of +    // plain wchar_t*. That said, wunder() returns std::basic_string<__wchar_t>, +    // NOT plain __wchar_t*, despite the apparent convenience. Calling +    // wunder(something).c_str() as an argument expression is fine: that +    // std::basic_string instance will survive until the function returns. +    // Calling c_str() on a std::basic_string local to wunder() would be +    // Undefined Behavior: we'd be left with a pointer into a destroyed +    // std::basic_string instance. But we can do that with a macro... +    #define WCSTR(string) wunder(string).c_str() + +    // It would be nice if, when wchar_t is the same as __wchar_t, this whole +    // function would optimize away. However, we use it only for the arguments +    // to the BugSplat API -- a handful of calls. +    inline std::basic_string<__wchar_t> wunder(const std::wstring& str) +    { +        return { str.begin(), str.end() }; +    } + +    // when what we have in hand is a std::string, convert from UTF-8 using +    // specific wstringize() overload +    inline std::basic_string<__wchar_t> wunder(const std::string& str) +    { +        return wunder(wstringize(str)); +    } + +    // Irritatingly, MiniDmpSender::setCallback() is defined to accept a +    // classic-C function pointer instead of an arbitrary C++ callable. If it +    // did accept a modern callable, we could pass a lambda that binds our +    // MiniDmpSender pointer. As things stand, though, we must define an +    // actual function and store the pointer statically. +    static MiniDmpSender *sBugSplatSender = nullptr; + +    bool bugsplatSendLog(UINT nCode, LPVOID lpVal1, LPVOID lpVal2) +    { +        if (nCode == MDSCB_EXCEPTIONCODE) +        { +            // send the main viewer log file +            // widen to wstring, convert to __wchar_t, then pass c_str() +            sBugSplatSender->sendAdditionalFile( +                WCSTR(gDirUtilp->getExpandedFilename(LL_PATH_LOGS, "SecondLife.log"))); + +            sBugSplatSender->sendAdditionalFile( +                WCSTR(gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "settings.xml"))); + +            sBugSplatSender->sendAdditionalFile( +                WCSTR(*LLAppViewer::instance()->getStaticDebugFile())); + +            // We don't have an email address for any user. Hijack this +            // metadata field for the platform identifier. +            sBugSplatSender->setDefaultUserEmail( +                WCSTR(STRINGIZE(LLOSInfo::instance().getOSStringSimple() << " (" +                                << ADDRESS_SIZE << "-bit)"))); + +            if (gAgentAvatarp) +            { +                // user name, when we have it +                sBugSplatSender->setDefaultUserName(WCSTR(gAgentAvatarp->getFullname())); +            } + +            // LL_ERRS message, when there is one +            sBugSplatSender->setDefaultUserDescription(WCSTR(LLError::getFatalMessage())); + +            if (gAgent.getRegion()) +            { +                // region location, when we have it +                LLVector3 loc = gAgent.getPositionAgent(); +                sBugSplatSender->resetAppIdentifier( +                    WCSTR(STRINGIZE(gAgent.getRegion()->getName() +                                    << '/' << loc.mV[0] +                                    << '/' << loc.mV[1] +                                    << '/' << loc.mV[2]))); +            } +        } // MDSCB_EXCEPTIONCODE + +        return false; +    } +} +#endif // LL_BUGSPLAT +  namespace  {      void (*gOldTerminateHandler)() = NULL; @@ -495,15 +588,71 @@ bool LLAppViewerWin32::init()  	LLWinDebug::instance();  #endif -#if LL_WINDOWS  #if LL_SEND_CRASH_REPORTS - +#if ! defined(LL_BUGSPLAT) +#pragma message("Building without BugSplat")  	LLAppViewer* pApp = LLAppViewer::instance();  	pApp->initCrashReporting(); -#endif -#endif +#else // LL_BUGSPLAT +#pragma message("Building with BugSplat") + +	std::string build_data_fname( +		gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, "build_data.json")); +	// Use llifstream instead of std::ifstream because LL_PATH_EXECUTABLE +	// could contain non-ASCII characters, which std::ifstream doesn't handle. +	llifstream inf(build_data_fname.c_str()); +	if (! inf.is_open()) +	{ +		LL_WARNS() << "Can't initialize BugSplat, can't read '" << build_data_fname +				   << "'" << LL_ENDL; +	} +	else +	{ +		Json::Reader reader; +		Json::Value build_data; +		if (! reader.parse(inf, build_data, false)) // don't collect comments +		{ +			// gah, the typo is baked into Json::Reader API +			LL_WARNS() << "Can't initialize BugSplat, can't parse '" << build_data_fname +					   << "': " << reader.getFormatedErrorMessages() << LL_ENDL; +		} +		else +		{ +			Json::Value BugSplat_DB = build_data["BugSplat DB"]; +			if (! BugSplat_DB) +			{ +				LL_WARNS() << "Can't initialize BugSplat, no 'BugSplat DB' entry in '" +						   << build_data_fname << "'" << LL_ENDL; +			} +			else +			{ +				// Got BugSplat_DB, onward! +				std::wstring version_string(WSTRINGIZE(LL_VIEWER_VERSION_MAJOR << '.' << +													   LL_VIEWER_VERSION_MINOR << '.' << +													   LL_VIEWER_VERSION_PATCH << '.' << +													   LL_VIEWER_VERSION_BUILD)); + +				// have to convert normal wide strings to strings of __wchar_t +				sBugSplatSender = new MiniDmpSender( +					WCSTR(BugSplat_DB.asString()), +					WCSTR(LL_TO_WSTRING(LL_VIEWER_CHANNEL)), +					WCSTR(version_string), +					nullptr,              // szAppIdentifier -- set later +					MDSF_NONINTERACTIVE | // automatically submit report without prompting +					MDSF_PREVENTHIJACKING); // disallow swiping Exception filter +				sBugSplatSender->setCallback(bugsplatSendLog); + +				// engage stringize() overload that converts from wstring +				LL_INFOS() << "Engaged BugSplat(" << LL_TO_STRING(LL_VIEWER_CHANNEL) +						   << ' ' << stringize(version_string) << ')' << LL_ENDL; +			} // got BugSplat_DB +		} // parsed build_data.json +	} // opened build_data.json + +#endif // LL_BUGSPLAT +#endif // LL_SEND_CRASH_REPORTS  	bool success = LLAppViewer::init(); diff --git a/indra/newview/llexternaleditor.cpp b/indra/newview/llexternaleditor.cpp index df9c848cb8..776bbf78c2 100644 --- a/indra/newview/llexternaleditor.cpp +++ b/indra/newview/llexternaleditor.cpp @@ -31,6 +31,7 @@  #include "llui.h"  #include "llprocess.h"  #include "llsdutil.h" +#include "llstring.h"  #include <boost/foreach.hpp>  // static @@ -188,12 +189,12 @@ std::string LLExternalEditor::findCommand(  		cmd = LLUI::sSettingGroups["config"]->getString(sSetting);  		LL_INFOS() << "Using setting" << LL_ENDL;  	} -	else					// otherwise use the path specified by the environment variable +	else                    // otherwise use the path specified by the environment variable  	{ -		char* env_var_val = getenv(env_var.c_str()); +		auto env_var_val(LLStringUtil::getoptenv(env_var));  		if (env_var_val)  		{ -			cmd = env_var_val; +			cmd = *env_var_val;  			LL_INFOS() << "Using env var " << env_var << LL_ENDL;  		}  	} diff --git a/indra/newview/lllogininstance.cpp b/indra/newview/lllogininstance.cpp index bc93fa2c20..126c6be388 100644 --- a/indra/newview/lllogininstance.cpp +++ b/indra/newview/lllogininstance.cpp @@ -253,14 +253,12 @@ bool LLLoginInstance::handleLoginEvent(const LLSD& event)  	mLoginState = event["state"].asString();  	mResponseData = event["data"]; -	 +  	if(event.has("transfer_rate"))  	{  		mTransferRate = event["transfer_rate"].asReal();  	} -	 -  	// Call the method registered in constructor, if any, for more specific  	// handling  	mDispatcher.try_call(event); @@ -276,6 +274,14 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)      // Login has failed.       // Figure out why and respond...      LLSD response = event["data"]; +    LLSD updater  = response["updater"]; + +    // Always provide a response to the updater, if in fact the updater +    // contacted us, if in fact the ping contains a 'reply' key. Most code +    // paths tell it not to proceed with updating. +    ResponsePtr resp(std::make_shared<LLEventAPI::Response> +                         (LLSDMap("update", false), updater)); +      std::string reason_response = response["reason"].asString();      std::string message_response = response["message"].asString();      LL_DEBUGS("LLLogin") << "reason " << reason_response @@ -328,17 +334,44 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)      }      else if(reason_response == "update")      { -        // This shouldn't happen - the viewer manager should have forced an update;  -        // possibly the user ran the viewer directly and bypassed the update check +        // This can happen if the user clicked Login quickly, before we heard +        // back from the Viewer Version Manager, but login failed because +        // login.cgi is insisting on a required update. We were called with an +        // event that bundles both the login.cgi 'response' and the +        // synchronization event from the 'updater'.          std::string required_version = response["message_args"]["VERSION"];          LL_WARNS("LLLogin") << "Login failed because an update to version " << required_version << " is required." << LL_ENDL;          if (gViewerWindow)              gViewerWindow->setShowProgress(FALSE); -        LLSD data(LLSD::emptyMap()); -        data["VERSION"] = required_version; -        LLNotificationsUtil::add("RequiredUpdate", data, LLSD::emptyMap(), boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2)); +        LLSD args(LLSDMap("VERSION", required_version)); +        if (updater.isUndefined()) +        { +            // If the updater failed to shake hands, better advise the user to +            // download the update him/herself. +            LLNotificationsUtil::add( +                "RequiredUpdate", +                args, +                updater, +                boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2)); +        } +        else +        { +            // If we've heard from the updater that an update is required, +            // then display the prompt that assures the user we'll take care +            // of it. This is the one case in which we bind 'resp': +            // instead of destroying our Response object (and thus sending a +            // negative reply to the updater) as soon as we exit this +            // function, bind our shared_ptr so it gets passed into +            // syncWithUpdater. That ensures that the response is delayed +            // until the user has responded to the notification. +            LLNotificationsUtil::add( +                "PauseForUpdate", +                args, +                updater, +                boost::bind(&LLLoginInstance::syncWithUpdater, this, resp, _1, _2)); +        }      }      else if(   reason_response == "key"              || reason_response == "presence" @@ -361,6 +394,19 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)      }     } +void LLLoginInstance::syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response) +{ +    LL_INFOS("LLLogin") << "LLLoginInstance::syncWithUpdater" << LL_ENDL; +    // 'resp' points to an instance of LLEventAPI::Response that will be +    // destroyed as soon as we return and the notification response functor is +    // unregistered. Modify it so that it tells the updater to go ahead and +    // perform the update. Naturally, if we allowed the user a choice as to +    // whether to proceed or not, this assignment would reflect the user's +    // selection. +    (*resp)["update"] = true; +    attemptComplete(); +} +  void LLLoginInstance::handleLoginDisallowed(const LLSD& notification, const LLSD& response)  {      attemptComplete(); @@ -420,7 +466,6 @@ bool LLLoginInstance::handleTOSResponse(bool accepted, const std::string& key)  	return true;  } -  std::string construct_start_string()  {  	std::string start; diff --git a/indra/newview/lllogininstance.h b/indra/newview/lllogininstance.h index 651ad10afb..b759b43474 100644 --- a/indra/newview/lllogininstance.h +++ b/indra/newview/lllogininstance.h @@ -28,8 +28,10 @@  #define LL_LLLOGININSTANCE_H  #include "lleventdispatcher.h" +#include "lleventapi.h"  #include <boost/scoped_ptr.hpp>  #include <boost/function.hpp> +#include <memory>                   // std::shared_ptr  #include "llsecapi.h"  class LLLogin;  class LLEventStream; @@ -68,6 +70,7 @@ public:  	LLNotificationsInterface& getNotificationsInterface() const { return *mNotifications; }  private: +	typedef std::shared_ptr<LLEventAPI::Response> ResponsePtr;  	void constructAuthParams(LLPointer<LLCredential> user_credentials);  	void updateApp(bool mandatory, const std::string& message);  	bool updateDialogCallback(const LLSD& notification, const LLSD& response); @@ -77,7 +80,8 @@ private:  	void handleLoginSuccess(const LLSD& event);  	void handleDisconnect(const LLSD& event);  	void handleIndeterminate(const LLSD& event); -    void handleLoginDisallowed(const LLSD& notification, const LLSD& response); +	void handleLoginDisallowed(const LLSD& notification, const LLSD& response); +	void syncWithUpdater(ResponsePtr resp, const LLSD& notification, const LLSD& response);  	bool handleTOSResponse(bool v, const std::string& key); diff --git a/indra/newview/llstartup.cpp b/indra/newview/llstartup.cpp index 1d24df5886..c5da7dc1b0 100644 --- a/indra/newview/llstartup.cpp +++ b/indra/newview/llstartup.cpp @@ -479,8 +479,7 @@ bool idle_startup()  			if (!found_template)  			{  				message_template_path = -					gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, -												   "../Resources/app_settings", +					gDirUtilp->getExpandedFilename(LL_PATH_APP_SETTINGS,  												   "message_template.msg");  				found_template = LLFile::fopen(message_template_path.c_str(), "r");		/* Flawfinder: ignore */  			}		 diff --git a/indra/newview/llversioninfo.cpp b/indra/newview/llversioninfo.cpp index 375dce485d..4e07223784 100644 --- a/indra/newview/llversioninfo.cpp +++ b/indra/newview/llversioninfo.cpp @@ -101,14 +101,11 @@ namespace  {  	// LL_VIEWER_CHANNEL is a macro defined on the compiler command line. The  	// macro expands to the string name of the channel, but without quotes. We -	// need to turn it into a quoted string. This macro trick does that. -#define stringize_inner(x) #x -#define stringize_outer(x) stringize_inner(x) - +	// need to turn it into a quoted string. LL_TO_STRING() does that.  	/// Storage of the channel name the viewer is using.  	//  The channel name is set by hardcoded constant,   	//  or by calling LLVersionInfo::resetChannel() -	std::string sWorkingChannelName(stringize_outer(LL_VIEWER_CHANNEL)); +	std::string sWorkingChannelName(LL_TO_STRING(LL_VIEWER_CHANNEL));  	// Storage for the "version and channel" string.  	// This will get reset too. diff --git a/indra/newview/llviewerregion.cpp b/indra/newview/llviewerregion.cpp index 5844dea1be..1c4f7c7b4d 100644 --- a/indra/newview/llviewerregion.cpp +++ b/indra/newview/llviewerregion.cpp @@ -44,6 +44,7 @@  #include "llagent.h"  #include "llagentcamera.h" +#include "llappviewer.h"  #include "llavatarrenderinfoaccountant.h"  #include "llcallingcard.h"  #include "llcommandhandler.h" @@ -106,6 +107,18 @@ typedef std::map<std::string, std::string> CapabilityMap;  static void log_capabilities(const CapabilityMap &capmap); +namespace +{ + +void newRegionEntry(LLViewerRegion& region) +{ +    LL_INFOS("LLViewerRegion") << "Entering region [" << region.getName() << "]" << LL_ENDL; +    gDebugInfo["CurrentRegion"] = region.getName(); +    LLAppViewer::instance()->writeDebugInfo(); +} + +} // anonymous namespace +  // support for secondlife:///app/region/{REGION} SLapps  // N.B. this is defined to work exactly like the classic secondlife://{REGION}  // However, the later syntax cannot support spaces in the region name because @@ -253,6 +266,9 @@ void LLViewerRegionImpl::requestBaseCapabilitiesCoro(U64 regionHandle)              return; // this error condition is not recoverable.          } +        // record that we just entered a new region +        newRegionEntry(*regionp); +          // After a few attempts, continue login.  But keep trying to get the caps:          if (mSeedCapAttempts >= mSeedCapMaxAttemptsBeforeLogin &&              STATE_SEED_GRANTED_WAIT == LLStartUp::getStartupState()) @@ -376,6 +392,9 @@ void LLViewerRegionImpl::requestBaseCapabilitiesCompleteCoro(U64 regionHandle)              break; // this error condition is not recoverable.          } +        // record that we just entered a new region +        newRegionEntry(*regionp); +          LLSD capabilityNames = LLSD::emptyArray();          buildCapabilityNames(capabilityNames); diff --git a/indra/newview/llviewerwindow.cpp b/indra/newview/llviewerwindow.cpp index 56111e9495..11cf303753 100644 --- a/indra/newview/llviewerwindow.cpp +++ b/indra/newview/llviewerwindow.cpp @@ -306,6 +306,9 @@ private:  RecordToChatConsole::RecordToChatConsole():  	mRecorder(new RecordToChatConsoleRecorder())  { +    mRecorder->showTags(false); +    mRecorder->showLocation(false); +    mRecorder->showMultiline(true);  }  //////////////////////////////////////////////////////////////////////////// diff --git a/indra/newview/llvoicevivox.cpp b/indra/newview/llvoicevivox.cpp index ee333bcee2..cf40058c34 100644 --- a/indra/newview/llvoicevivox.cpp +++ b/indra/newview/llvoicevivox.cpp @@ -770,14 +770,13 @@ bool LLVivoxVoiceClient::startAndLaunchDaemon()      {  #ifndef VIVOXDAEMON_REMOTEHOST          // Launch the voice daemon -        std::string exe_path = gDirUtilp->getExecutableDir(); -        exe_path += gDirUtilp->getDirDelimiter(); +        std::string exe_path = gDirUtilp->getAppRODataDir();  #if LL_WINDOWS -        exe_path += "SLVoice.exe"; +        gDirUtilp->append(exe_path, "SLVoice.exe");  #elif LL_DARWIN -        exe_path += "../Resources/SLVoice"; +        gDirUtilp->append(exe_path, "SLVoice");  #else -        exe_path += "SLVoice"; +        gDirUtilp->append(exe_path, "SLVoice");  #endif          // See if the vivox executable exists          llstat s; diff --git a/indra/newview/llwebprofile.cpp b/indra/newview/llwebprofile.cpp index 81d4e30a7a..8dcef2c7cd 100644 --- a/indra/newview/llwebprofile.cpp +++ b/indra/newview/llwebprofile.cpp @@ -33,6 +33,7 @@  #include "llimagepng.h"  #include "llsdserialize.h" +#include "llstring.h"  // newview  #include "llpanelprofile.h" // for getProfileURL(). FIXME: move the method to LLAvatarActions @@ -264,6 +265,5 @@ void LLWebProfile::reportImageUploadStatus(bool ok)  std::string LLWebProfile::getAuthCookie()  {  	// This is needed to test image uploads on Linux viewer built with OpenSSL 1.0.0 (0.9.8 works fine). -	const char* debug_cookie = getenv("LL_SNAPSHOT_COOKIE"); -	return debug_cookie ? debug_cookie : sAuthCookie; +	return LLStringUtil::getenv("LL_SNAPSHOT_COOKIE", sAuthCookie);  } diff --git a/indra/newview/skins/default/xui/en/notifications.xml b/indra/newview/skins/default/xui/en/notifications.xml index 5ed3bd5df5..8d6a787133 100644 --- a/indra/newview/skins/default/xui/en/notifications.xml +++ b/indra/newview/skins/default/xui/en/notifications.xml @@ -3851,7 +3851,6 @@ Finished download of raw terrain file to:     name="RequiredUpdate"     type="alertmodal">  Version [VERSION] is required for login. -This should have been updated for you but apparently was not.  Please download from https://secondlife.com/support/downloads/      <tag>confirm</tag>      <usetemplate @@ -3861,6 +3860,44 @@ Please download from https://secondlife.com/support/downloads/    <notification     icon="alertmodal.tga" +   name="PauseForUpdate" +   type="alertmodal"> +Version [VERSION] is required for login. +Click OK to download and install. +    <tag>confirm</tag> +    <usetemplate +     name="okbutton" +     yestext="OK"/> +  </notification> + +  <notification +   icon="alertmodal.tga" +   name="OptionalUpdateReady" +   type="alertmodal"> +Version [VERSION] has been downloaded and is ready to install. +Click OK to install. +    <tag>confirm</tag> +    <usetemplate +     name="okbutton" +     yestext="OK"/> +  </notification> + +  <notification +   icon="alertmodal.tga" +   name="PromptOptionalUpdate" +   type="alertmodal"> +Version [VERSION] has been downloaded and is ready to install. +Proceed? +    <tag>confirm</tag> +    <usetemplate +     canceltext="Not Now" +     name="yesnocancelbuttons" +     notext="Skip" +     yestext="Install"/> +  </notification> + +  <notification +   icon="alertmodal.tga"     name="LoginFailedUnknown"     type="alertmodal">  Sorry, login failed for an unrecognized reason. diff --git a/indra/newview/tests/llversioninfo_test.cpp b/indra/newview/tests/llversioninfo_test.cpp index 2f7a4e9601..58f0469552 100644 --- a/indra/newview/tests/llversioninfo_test.cpp +++ b/indra/newview/tests/llversioninfo_test.cpp @@ -33,10 +33,8 @@  // LL_VIEWER_CHANNEL is a macro defined on the compiler command line. The  // macro expands to the string name of the channel, but without quotes. We -// need to turn it into a quoted string. This macro trick does that. -#define stringize_inner(x) #x -#define stringize_outer(x) stringize_inner(x) -#define ll_viewer_channel stringize_outer(LL_VIEWER_CHANNEL) +// need to turn it into a quoted string. LL_TO_STRING() does that. +#define ll_viewer_channel LL_TO_STRING(LL_VIEWER_CHANNEL)  namespace tut  { diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py index 541112a765..c0f642c852 100755 --- a/indra/newview/viewer_manifest.py +++ b/indra/newview/viewer_manifest.py @@ -26,19 +26,20 @@ 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 -import os.path -import shutil  import errno  import json +import os +import os.path  import plistlib  import random  import re +import shutil  import stat  import subprocess +import sys  import tarfile  import time +import zipfile  viewer_dir = os.path.dirname(__file__)  # Add indra/lib/python to our path so we don't have to muck with PYTHONPATH. @@ -63,7 +64,7 @@ class ViewerManifest(LLManifest):          self.path(src="../../etc/message.xml", dst="app_settings/message.xml")          if self.is_packaging_viewer(): -            with self.prefix(src="app_settings"): +            with self.prefix(src_dst="app_settings"):                  self.exclude("logcontrol.xml")                  self.exclude("logcontrol-dev.xml")                  self.path("*.ini") @@ -85,7 +86,7 @@ class ViewerManifest(LLManifest):                  # ... and the included spell checking dictionaries                  pkgdir = os.path.join(self.args['build'], os.pardir, 'packages') -                with self.prefix(src=pkgdir,dst=""): +                with self.prefix(src=pkgdir):                      self.path("dictionaries")                  # include the extracted packages information (see BuildPackagesInfo.cmake) @@ -107,17 +108,18 @@ class ViewerManifest(LLManifest):                                          Type='String',                                          Value=''))                  settings_install = {} -                if 'sourceid' in self.args and self.args['sourceid']: +                sourceid = self.args.get('sourceid') +                if sourceid:                      settings_install['sourceid'] = settings_template['sourceid'].copy() -                    settings_install['sourceid']['Value'] = self.args['sourceid'] -                    print "Set sourceid in settings_install.xml to '%s'" % self.args['sourceid'] +                    settings_install['sourceid']['Value'] = sourceid +                    print "Set sourceid in settings_install.xml to '%s'" % sourceid -                if 'channel_suffix' in self.args and self.args['channel_suffix']: +                if self.args.get('channel_suffix'):                      settings_install['CmdLineChannel'] = settings_template['CmdLineChannel'].copy()                      settings_install['CmdLineChannel']['Value'] = self.channel_with_pkg_suffix()                      print "Set CmdLineChannel in settings_install.xml to '%s'" % self.channel_with_pkg_suffix() -                if 'grid' in self.args and self.args['grid']: +                if self.args.get('grid'):                      settings_install['CmdLineGridChoice'] = settings_template['CmdLineGridChoice'].copy()                      settings_install['CmdLineGridChoice']['Value'] = self.grid()                      print "Set CmdLineGridChoice in settings_install.xml to '%s'" % self.grid() @@ -129,20 +131,20 @@ class ViewerManifest(LLManifest):                                   src="environment") -            with self.prefix(src="character"): +            with self.prefix(src_dst="character"):                  self.path("*.llm")                  self.path("*.xml")                  self.path("*.tga")              # Include our fonts -            with self.prefix(src="fonts"): +            with self.prefix(src_dst="fonts"):                  self.path("*.ttf")                  self.path("*.txt")              # skins -            with self.prefix(src="skins"): +            with self.prefix(src_dst="skins"):                      # include the entire textures directory recursively -                    with self.prefix(src="*/textures"): +                    with self.prefix(src_dst="*/textures"):                              self.path("*/*.tga")                              self.path("*/*.j2c")                              self.path("*/*.jpg") @@ -170,7 +172,7 @@ class ViewerManifest(LLManifest):              # local_assets dir (for pre-cached textures) -            with self.prefix(src="local_assets"): +            with self.prefix(src_dst="local_assets"):                  self.path("*.j2c")                  self.path("*.tga") @@ -186,6 +188,10 @@ class ViewerManifest(LLManifest):                              "Address Size":self.address_size,                              "Update Service":"https://update.secondlife.com/update",                              } +            # Only store this if it's both present and non-empty +            bugsplat_db = self.args.get('bugsplat') +            if bugsplat_db: +                build_data_dict["BugSplat DB"] = bugsplat_db              build_data_dict = self.finish_build_data_dict(build_data_dict)              with open(os.path.join(os.pardir,'build_data.json'), 'w') as build_data_handle:                  json.dump(build_data_dict,build_data_handle) @@ -206,8 +212,9 @@ class ViewerManifest(LLManifest):      def channel_with_pkg_suffix(self):          fullchannel=self.channel() -        if 'channel_suffix' in self.args and self.args['channel_suffix']: -            fullchannel+=' '+self.args['channel_suffix'] +        channel_suffix = self.args.get('channel_suffix') +        if channel_suffix: +            fullchannel+=' '+channel_suffix          return fullchannel      def channel_variant(self): @@ -215,8 +222,7 @@ class ViewerManifest(LLManifest):          return self.channel().replace(CHANNEL_VENDOR_BASE, "").strip()      def channel_type(self): # returns 'release', 'beta', 'project', or 'test' -        global CHANNEL_VENDOR_BASE -        channel_qualifier=self.channel().replace(CHANNEL_VENDOR_BASE, "").lower().strip() +        channel_qualifier=self.channel_variant().lower()          if channel_qualifier.startswith('release'):              channel_type='release'          elif channel_qualifier.startswith('beta'): @@ -234,11 +240,12 @@ class ViewerManifest(LLManifest):          if self.channel_type() == 'release':              suffix=suffix.replace('Release', '').strip()          # for the base release viewer, suffix will now be null - for any other, append what remains -        if len(suffix) > 0: -            suffix = "_"+ ("_".join(suffix.split())) +        if suffix: +            suffix = "_".join([''] + suffix.split())          # the additional_packages mechanism adds more to the installer name (but not to the app name itself) -        if 'channel_suffix' in self.args and self.args['channel_suffix']: -            suffix+='_'+("_".join(self.args['channel_suffix'].split())) +        # ''.split() produces empty list, so suffix only changes if +        # channel_suffix is non-empty +        suffix = "_".join([suffix] + self.args.get('channel_suffix', '').split())          return suffix      def installer_base_name(self): @@ -424,7 +431,8 @@ class WindowsManifest(ViewerManifest):      def finish_build_data_dict(self, build_data_dict):          #MAINT-7294: Windows exe names depend on channel name, so write that in also -        build_data_dict.update({'Executable':self.final_exe()}) +        build_data_dict['Executable'] = self.final_exe() +        build_data_dict['AppName']    = self.app_name()          return build_data_dict      def test_msvcrt_and_copy_action(self, src, dst): @@ -470,7 +478,7 @@ class WindowsManifest(ViewerManifest):                      pass                  except NoMatchingAssemblyException as err:                      pass -                     +                  self.ccopy(src,dst)              else:                  raise Exception("Directories are not supported by test_CRT_and_copy_action()") @@ -488,24 +496,17 @@ class WindowsManifest(ViewerManifest):              # 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()) -            with self.prefix(src=os.path.join(pkgdir, "VMP"), dst=""): +            with self.prefix(src=os.path.join(pkgdir, "VMP")):                  # include the compiled launcher scripts so that it gets included in the file_list -                self.path('SL_Launcher.exe') -                #IUM is not normally executed directly, just imported.  No exe needed. -                self.path("InstallerUserMessage.py") - -            with self.prefix(src=self.icon_path(), dst="vmp_icons"): -                self.path("secondlife.ico") +                self.path('SLVersionChecker.exe') -            #VMP  Tkinter icons -            with self.prefix("vmp_icons"): -                self.path("*.png") -                self.path("*.gif") - -            #before, we only needed llbase at build time.  With VMP, we need it at run time. -            with self.prefix(src=os.path.join(pkgdir, "lib", "python", "llbase"), dst="llbase"): -                self.path("*.py") -                self.path("_cllsd.so") +            with self.prefix(dst="vmp_icons"): +                with self.prefix(src=self.icon_path()): +                    self.path("secondlife.ico") +                #VMP  Tkinter icons +                with self.prefix(src="vmp_icons"): +                    self.path("*.png") +                    self.path("*.gif")          # Plugin host application          self.path2basename(os.path.join(os.pardir, @@ -513,8 +514,8 @@ class WindowsManifest(ViewerManifest):                             "slplugin.exe")          # Get shared libs from the shared libs staging directory -        with self.prefix(src=os.path.join(os.pardir, 'sharedlibs', self.args['configuration']), -                       dst=""): +        with self.prefix(src=os.path.join(self.args['build'], os.pardir, +                                          'sharedlibs', self.args['configuration'])):              # Get llcommon and deps. If missing assume static linkage and continue.              try: @@ -580,6 +581,17 @@ class WindowsManifest(ViewerManifest):              # Hunspell              self.path("libhunspell.dll") +            # BugSplat +            if self.args.get('bugsplat'): +                if(self.address_size == 64): +                    self.path("BsSndRpt64.exe") +                    self.path("BugSplat64.dll") +                    self.path("BugSplatRc64.dll") +                else: +                    self.path("BsSndRpt.exe") +                    self.path("BugSplat.dll") +                    self.path("BugSplatRc.dll") +              # For google-perftools tcmalloc allocator.              try:                  if self.args['configuration'].lower() == 'debug': @@ -589,116 +601,118 @@ class WindowsManifest(ViewerManifest):              except:                  print "Skipping libtcmalloc_minimal.dll" -          self.path(src="licenses-win32.txt", dst="licenses.txt")          self.path("featuretable.txt") -        with self.prefix(src=pkgdir,dst=""): +        with self.prefix(src=pkgdir):              self.path("ca-bundle.crt")          # Media plugins - CEF -        with self.prefix(src='../media_plugins/cef/%s' % self.args['configuration'], dst="llplugin"): -            self.path("media_plugin_cef.dll") - -        # Media plugins - LibVLC -        with self.prefix(src='../media_plugins/libvlc/%s' % self.args['configuration'], dst="llplugin"): -            self.path("media_plugin_libvlc.dll") - -        # Media plugins - Example (useful for debugging - not shipped with release viewer) -        if self.channel_type() != 'release': -            with self.prefix(src='../media_plugins/example/%s' % self.args['configuration'], dst="llplugin"): -                self.path("media_plugin_example.dll") - -        # CEF runtime files - debug -        # CEF runtime files - not debug (release, relwithdebinfo etc.) -        config = 'debug' if self.args['configuration'].lower() == 'debug' else 'release' -        with self.prefix(src=os.path.join(pkgdir, 'bin', config), dst="llplugin"): -            self.path("chrome_elf.dll") -            self.path("d3dcompiler_43.dll") -            self.path("d3dcompiler_47.dll") -            self.path("libcef.dll") -            self.path("libEGL.dll") -            self.path("libGLESv2.dll") -            self.path("dullahan_host.exe") -            self.path("natives_blob.bin") -            self.path("snapshot_blob.bin") -            self.path("widevinecdmadapter.dll") - -        # MSVC DLLs needed for CEF and have to be in same directory as plugin -        with self.prefix(src=os.path.join(os.pardir, 'sharedlibs', 'Release'), dst="llplugin"): -            self.path("msvcp120.dll") -            self.path("msvcr120.dll") - -        # CEF files common to all configurations -        with self.prefix(src=os.path.join(pkgdir, 'resources'), dst="llplugin"): -            self.path("cef.pak") -            self.path("cef_100_percent.pak") -            self.path("cef_200_percent.pak") -            self.path("cef_extensions.pak") -            self.path("devtools_resources.pak") -            self.path("icudtl.dat") - -        with self.prefix(src=os.path.join(pkgdir, 'resources', 'locales'), dst=os.path.join('llplugin', 'locales')): -            self.path("am.pak") -            self.path("ar.pak") -            self.path("bg.pak") -            self.path("bn.pak") -            self.path("ca.pak") -            self.path("cs.pak") -            self.path("da.pak") -            self.path("de.pak") -            self.path("el.pak") -            self.path("en-GB.pak") -            self.path("en-US.pak") -            self.path("es-419.pak") -            self.path("es.pak") -            self.path("et.pak") -            self.path("fa.pak") -            self.path("fi.pak") -            self.path("fil.pak") -            self.path("fr.pak") -            self.path("gu.pak") -            self.path("he.pak") -            self.path("hi.pak") -            self.path("hr.pak") -            self.path("hu.pak") -            self.path("id.pak") -            self.path("it.pak") -            self.path("ja.pak") -            self.path("kn.pak") -            self.path("ko.pak") -            self.path("lt.pak") -            self.path("lv.pak") -            self.path("ml.pak") -            self.path("mr.pak") -            self.path("ms.pak") -            self.path("nb.pak") -            self.path("nl.pak") -            self.path("pl.pak") -            self.path("pt-BR.pak") -            self.path("pt-PT.pak") -            self.path("ro.pak") -            self.path("ru.pak") -            self.path("sk.pak") -            self.path("sl.pak") -            self.path("sr.pak") -            self.path("sv.pak") -            self.path("sw.pak") -            self.path("ta.pak") -            self.path("te.pak") -            self.path("th.pak") -            self.path("tr.pak") -            self.path("uk.pak") -            self.path("vi.pak") -            self.path("zh-CN.pak") -            self.path("zh-TW.pak") - -        with self.prefix(src=os.path.join(pkgdir, 'bin', 'release'), dst="llplugin"): -            self.path("libvlc.dll") -            self.path("libvlccore.dll") -            self.path("plugins/") - -        # pull in the crash logger and updater from other projects +        with self.prefix(dst="llplugin"): +            with self.prefix(src=os.path.join(self.args['build'], os.pardir, 'media_plugins')): +                with self.prefix(src=os.path.join('cef', self.args['configuration'])): +                    self.path("media_plugin_cef.dll") + +                # Media plugins - LibVLC +                with self.prefix(src=os.path.join('libvlc', self.args['configuration'])): +                    self.path("media_plugin_libvlc.dll") + +                # Media plugins - Example (useful for debugging - not shipped with release viewer) +                if self.channel_type() != 'release': +                    with self.prefix(src=os.path.join('example', self.args['configuration'])): +                        self.path("media_plugin_example.dll") + +            # CEF runtime files - debug +            # CEF runtime files - not debug (release, relwithdebinfo etc.) +            config = 'debug' if self.args['configuration'].lower() == 'debug' else 'release' +            with self.prefix(src=os.path.join(pkgdir, 'bin', config)): +                self.path("chrome_elf.dll") +                self.path("d3dcompiler_43.dll") +                self.path("d3dcompiler_47.dll") +                self.path("libcef.dll") +                self.path("libEGL.dll") +                self.path("libGLESv2.dll") +                self.path("dullahan_host.exe") +                self.path("natives_blob.bin") +                self.path("snapshot_blob.bin") +                self.path("widevinecdmadapter.dll") + +            # MSVC DLLs needed for CEF and have to be in same directory as plugin +            with self.prefix(src=os.path.join(self.args['build'], os.pardir, +                                              'sharedlibs', 'Release')): +                self.path("msvcp120.dll") +                self.path("msvcr120.dll") + +            # CEF files common to all configurations +            with self.prefix(src=os.path.join(pkgdir, 'resources')): +                self.path("cef.pak") +                self.path("cef_100_percent.pak") +                self.path("cef_200_percent.pak") +                self.path("cef_extensions.pak") +                self.path("devtools_resources.pak") +                self.path("icudtl.dat") + +            with self.prefix(src=os.path.join(pkgdir, 'resources', 'locales'), dst='locales'): +                self.path("am.pak") +                self.path("ar.pak") +                self.path("bg.pak") +                self.path("bn.pak") +                self.path("ca.pak") +                self.path("cs.pak") +                self.path("da.pak") +                self.path("de.pak") +                self.path("el.pak") +                self.path("en-GB.pak") +                self.path("en-US.pak") +                self.path("es-419.pak") +                self.path("es.pak") +                self.path("et.pak") +                self.path("fa.pak") +                self.path("fi.pak") +                self.path("fil.pak") +                self.path("fr.pak") +                self.path("gu.pak") +                self.path("he.pak") +                self.path("hi.pak") +                self.path("hr.pak") +                self.path("hu.pak") +                self.path("id.pak") +                self.path("it.pak") +                self.path("ja.pak") +                self.path("kn.pak") +                self.path("ko.pak") +                self.path("lt.pak") +                self.path("lv.pak") +                self.path("ml.pak") +                self.path("mr.pak") +                self.path("ms.pak") +                self.path("nb.pak") +                self.path("nl.pak") +                self.path("pl.pak") +                self.path("pt-BR.pak") +                self.path("pt-PT.pak") +                self.path("ro.pak") +                self.path("ru.pak") +                self.path("sk.pak") +                self.path("sl.pak") +                self.path("sr.pak") +                self.path("sv.pak") +                self.path("sw.pak") +                self.path("ta.pak") +                self.path("te.pak") +                self.path("th.pak") +                self.path("tr.pak") +                self.path("uk.pak") +                self.path("vi.pak") +                self.path("zh-CN.pak") +                self.path("zh-TW.pak") + +            with self.prefix(src=os.path.join(pkgdir, 'bin', 'release')): +                self.path("libvlc.dll") +                self.path("libvlccore.dll") +                self.path("plugins/") + +        # pull in the crash logger 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") @@ -768,7 +782,7 @@ class WindowsManifest(ViewerManifest):          substitution_strings['installer_file'] = installer_file          version_vars = """ -        !define INSTEXE "SL_Launcher.exe" +        !define INSTEXE "SLVersionChecker.exe"          !define VERSION "%(version_short)s"          !define VERSION_LONG "%(version)s"          !define VERSION_DASHES "%(version_dashes)s" @@ -791,10 +805,10 @@ class WindowsManifest(ViewerManifest):          if(self.address_size == 64):              engage_registry="SetRegView 64" -            program_files="$PROGRAMFILES64" +            program_files="!define MULTIUSER_USE_PROGRAMFILES64"          else:              engage_registry="SetRegView 32" -            program_files="$PROGRAMFILES32" +            program_files=""          tempfile = "secondlife_setup_tmp.nsi"          # the following replaces strings in the nsi template @@ -812,16 +826,15 @@ class WindowsManifest(ViewerManifest):          # note that the enclosing setup exe is signed later, after the makensis makes it.          # Unlike the viewer binary, the VMP filenames are invariant with respect to version, os, etc.          for exe in ( -            "SL_Launcher.exe", +            self.final_exe(), +            "SLVersionChecker.exe",              ):              self.sign(exe) -        # 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.          for ProgramFiles in 'ProgramFiles', 'ProgramFiles(x86)': -            NSIS_path = os.path.expandvars(r'${%s}\NSIS\Unicode\makensis.exe' % ProgramFiles) +            NSIS_path = os.path.expandvars(r'${%s}\NSIS\makensis.exe' % ProgramFiles)              if os.path.exists(NSIS_path):                  break          installer_created=False @@ -879,395 +892,327 @@ class DarwinManifest(ViewerManifest):          # darwin requires full app bundle packaging even for debugging.          return True -    def construct(self): -        # These are the names of the top-level application and the embedded -        # applications for the VMP and for the actual viewer, respectively. -        # These names, without the .app suffix, determine the flyover text for -        # their corresponding Dock icons. -        toplevel_app, toplevel_icon = "Second Life.app",          "secondlife.icns" -        launcher_app, launcher_icon = "Second Life Launcher.app", "secondlife.icns" -        viewer_app,   viewer_icon   = "Second Life Viewer.app",   "secondlife.icns" +    def is_rearranging(self): +        # That said, some stuff should still only be performed once. +        # Are either of these actions in 'actions'? Is the set intersection +        # non-empty? +        return bool(set(["package", "unpacked"]).intersection(self.args['actions'])) +    def construct(self):          # copy over the build result (this is a no-op if run within the xcode script) -        self.path(os.path.join(self.args['configuration'], toplevel_app), dst="") +        self.path(os.path.join(self.args['configuration'], self.channel()+".app"), dst="")          pkgdir = os.path.join(self.args['build'], os.pardir, 'packages')          relpkgdir = os.path.join(pkgdir, "lib", "release")          debpkgdir = os.path.join(pkgdir, "lib", "debug") -        # -------------------- top-level Second Life.app --------------------- -        # top-level Second Life application is only a container          with self.prefix(src="", dst="Contents"):  # everything goes in Contents -            # top-level Info.plist is as generated by CMake -            Info_plist = "Info.plist" -            ## This self.path() call reports 0 files... skip? -            self.path(Info_plist) -            Info_plist = self.dst_path_of(Info_plist) - -            # the one file in top-level MacOS directory is the trampoline to -            # our nested launcher_app +            bugsplat_db = self.args.get('bugsplat') +            if bugsplat_db: +                # Inject BugsplatServerURL into Info.plist if provided. +                Info_plist = self.dst_path_of("Info.plist") +                Info = plistlib.readPlist(Info_plist) +                # https://www.bugsplat.com/docs/platforms/os-x#configuration +                Info["BugsplatServerURL"] = \ +                    "https://{}.bugsplat.com/".format(bugsplat_db) +                self.put_in_file( +                    plistlib.writePlistToString(Info), +                    os.path.basename(Info_plist), +                    "Info.plist") + +            # CEF framework goes inside Contents/Frameworks. +            # Remember where we parked this car. +            with self.prefix(src="", dst="Frameworks"): +                CEF_framework = "Chromium Embedded Framework.framework" +                self.path2basename(relpkgdir, CEF_framework) +                CEF_framework = self.dst_path_of(CEF_framework) + +                if self.args.get('bugsplat'): +                    self.path2basename(relpkgdir, "BugsplatMac.framework") +              with self.prefix(dst="MacOS"): -                toplevel_MacOS = self.get_dst_prefix() -                trampoline = self.put_in_file("""\ -#!/bin/bash -open "%s" --args "$@" -""" % -                    # up one directory from MacOS to its sibling Resources directory -                    os.path.join('$(dirname "$0")', os.pardir, 'Resources', launcher_app), -                    "SL_Launcher",      # write this file -                    "trampoline")       # flag to add to list of copied files -                # Script must be executable -                self.run_command(["chmod", "+x", trampoline]) - -            # Make a symlink to a nested app Frameworks directory that doesn't -            # yet exist. We shouldn't need this; the only things that need -            # Frameworks are nested apps under viewer_app, and they should -            # simply find its Contents/Frameworks by relative pathnames. But -            # empirically, we do: if we omit this symlink, CEF doesn't work -- -            # the login splash screen doesn't even display. SIIIIGH. -            # We're passing a path that's already relative, hence symlinkf() -            # rather than relsymlinkf(). -            self.symlinkf(os.path.join("Resources", viewer_app, "Contents", "Frameworks")) - -            with self.prefix(src="", dst="Resources"): -                # top-level Resources directory should be pretty sparse -                # need .icns file referenced by top-level Info.plist +                executable = self.dst_path_of(self.channel()) +                if self.args.get('bugsplat'): +                    # According to Apple Technical Note TN2206: +                    # https://developer.apple.com/library/archive/technotes/tn2206/_index.html#//apple_ref/doc/uid/DTS40007919-CH1-TNTAG207 +                    # "If an app uses @rpath or an absolute path to link to a +                    # dynamic library outside of the app, the app will be +                    # rejected by Gatekeeper. ... Neither the codesign nor the +                    # spctl tool will show the error." +                    # (Thanks, Apple. Maybe fix spctl to warn?) +                    # The BugsplatMac framework embeds @rpath, which is +                    # causing scary Gatekeeper popups at viewer start. Work +                    # around this by changing the reference baked into our +                    # viewer. The install_name_tool -change option needs the +                    # previous value. Instead of guessing -- which might +                    # silently be defeated by a BugSplat SDK update that +                    # changes their baked-in @rpath -- ask for the path +                    # stamped into the framework. +                    # Let exception, if any, propagate -- if this doesn't +                    # work, we need the build to noisily fail! +                    oldpath = subprocess.check_output( +                        ['objdump', '-macho', '-dylib-id', '-non-verbose', +                         os.path.join(relpkgdir, "BugsplatMac.framework", "BugsplatMac")] +                        ).splitlines()[-1]  # take the last line of output +                    self.run_command( +                        ['install_name_tool', '-change', oldpath, +                         '@executable_path/../Frameworks/BugsplatMac.framework/BugsplatMac', +                         executable]) + +                # 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', executable]) + +            with self.prefix(dst="Resources"): +                # defer cross-platform file copies until we're in the +                # nested Resources directory +                super(DarwinManifest, self).construct() + +                # need .icns file referenced by Info.plist                  with self.prefix(src=self.icon_path(), dst="") : -                    self.path(toplevel_icon) - -                # ------------------- nested launcher_app -------------------- -                with self.prefix(dst=os.path.join(launcher_app, "Contents")): -                    # Info.plist is just like top-level one... -                    Info = plistlib.readPlist(Info_plist) -                    # except for these replacements: -                    Info["CFBundleExecutable"] = "SL_Launcher" -                    Info["CFBundleIconFile"] = launcher_icon -                    self.put_in_file( -                        plistlib.writePlistToString(Info), -                        os.path.basename(Info_plist), -                        "Info.plist") - -                    # copy VMP libs to MacOS -                    with self.prefix(dst="MacOS"):               -                        #this copies over the python wrapper script, -                        #associated utilities and required libraries, see -                        #SL-321, SL-322, SL-323 -                        with self.prefix(src=os.path.join(pkgdir, "VMP"), dst=""): -                            self.path("SL_Launcher") -                            self.path("*.py") -                            # certifi will be imported by requests; this is -                            # our custom version to get our ca-bundle.crt -                            self.path("certifi") -                        with self.prefix(src=os.path.join(pkgdir, "lib", "python"), dst=""): -                            # llbase provides our llrest service layer and llsd decoding -                            with self.prefix("llbase"): -                                # (Why is llbase treated specially here? What -                                # DON'T we want to copy out of lib/python/llbase?) -                                self.path("*.py") -                                self.path("_cllsd.so") -                            #requests module needed by llbase/llrest.py -                            #this is only needed on POSIX, because in Windows -                            #we compile it into the EXE -                            for pypkg in "chardet", "idna", "requests", "urllib3": -                                self.path(pypkg) - -                    # launcher_app/Contents/Resources -                    with self.prefix(dst="Resources"): -                        with self.prefix(src=self.icon_path(), dst="") : -                            self.path(launcher_icon) -                            with self.prefix(dst="vmp_icons"): -                                self.path("secondlife.ico") -                        #VMP Tkinter icons -                        with self.prefix("vmp_icons"): -                            self.path("*.png") -                            self.path("*.gif") - -                # -------------------- nested viewer_app --------------------- -                with self.prefix(dst=os.path.join(viewer_app, "Contents")): -                    # Info.plist is just like top-level one... -                    Info = plistlib.readPlist(Info_plist) -                    # except for these replacements: -                    # (CFBundleExecutable may be moot: SL_Launcher directly -                    # runs the executable, instead of launching the app) -                    Info["CFBundleExecutable"] = "Second Life" -                    Info["CFBundleIconFile"] = viewer_icon -                    self.put_in_file( -                        plistlib.writePlistToString(Info), -                        os.path.basename(Info_plist), -                        "Info.plist") - -                    # CEF framework goes inside viewer_app/Contents/Frameworks. -                    # Remember where we parked this car. -                    with self.prefix(src="", dst="Frameworks"): -                        CEF_framework = "Chromium Embedded Framework.framework" -                        self.path2basename(relpkgdir, CEF_framework) -                        CEF_framework = self.dst_path_of(CEF_framework) - -                    with self.prefix(dst="MacOS"): -                        # CMake constructs the Second Life executable in the -                        # MacOS directory belonging to the top-level Second -                        # Life.app. Move it here. -                        here = self.get_dst_prefix() -                        relbase = os.path.realpath(os.path.dirname(Info_plist)) -                        self.cmakedirs(here) -                        for f in os.listdir(toplevel_MacOS): -                            if f == os.path.basename(trampoline): -                                # don't move the trampoline script we just made! -                                continue -                            fromwhere = os.path.join(toplevel_MacOS, f) -                            towhere   = os.path.join(here, f) -                            print "Moving %s => %s" % \ -                                  (self.relpath(fromwhere, relbase), -                                   self.relpath(towhere, relbase)) -                            # now do it, only without relativizing paths -                            os.rename(fromwhere, towhere) - -                        # 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', self.dst_path_of('Second Life')]) - -                    with self.prefix(dst="Resources"): -                        # defer cross-platform file copies until we're in the right -                        # nested Resources directory -                        super(DarwinManifest, self).construct() - -                        with self.prefix(src=self.icon_path(), dst="") : -                            self.path(viewer_icon) - -                        with self.prefix(src=relpkgdir, dst=""): -                            self.path("libndofdev.dylib") -                            self.path("libhunspell-1.3.0.dylib")    - -                        with self.prefix("cursors_mac"): -                            self.path("*.tif") - -                        self.path("licenses-mac.txt", dst="licenses.txt") -                        self.path("featuretable_mac.txt") -                        self.path("SecondLife.nib") - -                        with self.prefix(src=pkgdir,dst=""): -                            self.path("ca-bundle.crt") - -                        self.path("SecondLife.nib") - -                        # Translations -                        self.path("English.lproj/language.txt") -                        self.replace_in(src="English.lproj/InfoPlist.strings", -                                        dst="English.lproj/InfoPlist.strings", -                                        searchdict={'%%VERSION%%':'.'.join(self.args['version'])} -                                        ) -                        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") - -                        def path_optional(src, dst): -                            """ -                            For a number of our self.path() calls, not only do we want -                            to deal with the absence of src, we also want to remember -                            which were present. Return either an empty list (absent) -                            or a list containing dst (present). Concatenate these -                            return values to get a list of all libs that are present. -                            """ -                            # This was simple before we started needing to pass -                            # wildcards. Fortunately, self.path() ends up appending a -                            # (source, dest) pair to self.file_list for every expanded -                            # file processed. Remember its size before the call. -                            oldlen = len(self.file_list) -                            self.path(src, dst) -                            # The dest appended to self.file_list has been prepended -                            # with self.get_dst_prefix(). Strip it off again. -                            added = [os.path.relpath(d, self.get_dst_prefix()) -                                     for s, d in self.file_list[oldlen:]] -                            if not added: -                                print "Skipping %s" % dst -                            return added - -                        # dylibs is a list of all the .dylib files we expect to need -                        # in our bundled sub-apps. For each of these we'll create a -                        # symlink from sub-app/Contents/Resources to the real .dylib. -                        # Need to get the llcommon dll from any of the build directories as well. -                        libfile_parent = self.get_dst_prefix() -                        libfile = "libllcommon.dylib" -                        dylibs = path_optional(self.find_existing_file(os.path.join(os.pardir, -                                                                       "llcommon", -                                                                       self.args['configuration'], -                                                                       libfile), -                                                                       os.path.join(relpkgdir, libfile)), -                                               dst=libfile) - -                        for libfile in ( -                                        "libapr-1.0.dylib", -                                        "libaprutil-1.0.dylib", -                                        "libcollada14dom.dylib", -                                        "libexpat.1.dylib", -                                        "libexception_handler.dylib", -                                        "libGLOD.dylib", -                                        # libnghttp2.dylib is a symlink to -                                        # libnghttp2.major.dylib, which is a symlink to -                                        # libnghttp2.version.dylib. Get all of them. -                                        "libnghttp2.*dylib", -                                        ): -                            dylibs += path_optional(os.path.join(relpkgdir, libfile), libfile) - -                        # SLVoice and vivox lols, no symlinks needed -                        for libfile in ( -                                        'libortp.dylib', -                                        'libsndfile.dylib', -                                        'libvivoxoal.dylib', -                                        'libvivoxsdk.dylib', -                                        'libvivoxplatform.dylib', -                                        'SLVoice', -                                        ): -                            self.path2basename(relpkgdir, libfile) - -                        # dylibs that vary based on configuration -                        if self.args['configuration'].lower() == 'debug': -                            for libfile in ( -                                        "libfmodexL.dylib", -                                        ): -                                dylibs += path_optional(os.path.join(debpkgdir, libfile), libfile) -                        else: -                            for libfile in ( -                                        "libfmodex.dylib", -                                        ): -                                dylibs += path_optional(os.path.join(relpkgdir, libfile), libfile) - -                        # our apps -                        executable_path = {} -                        for app_bld_dir, app in (("mac_crash_logger", "mac-crash-logger.app"), -                                                 # plugin launcher -                                                 (os.path.join("llplugin", "slplugin"), "SLPlugin.app"), -                                                 ): -                            self.path2basename(os.path.join(os.pardir, -                                                            app_bld_dir, self.args['configuration']), -                                               app) -                            executable_path[app] = \ -                                self.dst_path_of(os.path.join(app, "Contents", "MacOS")) - -                            # our apps dependencies on shared libs -                            # for each app, for each dylib we collected in dylibs, -                            # create a symlink to the real copy of the dylib. -                            with self.prefix(dst=os.path.join(app, "Contents", "Resources")): -                                for libfile in dylibs: -                                    self.relsymlinkf(os.path.join(libfile_parent, libfile)) - -                        # Dullahan helper apps go inside SLPlugin.app -                        with self.prefix(dst=os.path.join( -                            "SLPlugin.app", "Contents", "Frameworks")): - -                            frameworkname = 'Chromium Embedded Framework' - -                            # This code constructs a relative symlink from the -                            # target framework folder back to the real CEF framework. -                            # It needs to be relative so that the symlink still works when -                            # (as is normal) the user moves the app bundle out of the DMG -                            # and into the /Applications folder. Note we pass catch=False, -                            # letting the uncaught exception terminate the process, since -                            # without this symlink, Second Life web media can't possibly work. - -                            # It might seem simpler just to symlink Frameworks back to -                            # the parent of Chromimum Embedded Framework.framework. But -                            # that would create a symlink cycle, which breaks our -                            # packaging step. So make a symlink from Chromium Embedded -                            # Framework.framework to the directory of the same name, which -                            # is NOT an ancestor of the symlink. - -                            # from SLPlugin.app/Contents/Frameworks/Chromium Embedded -                            # Framework.framework back to -                            # $viewer_app/Contents/Frameworks/Chromium Embedded Framework.framework -                            SLPlugin_framework = self.relsymlinkf(CEF_framework, catch=False) - -                            # copy DullahanHelper.app -                            self.path2basename(relpkgdir, 'DullahanHelper.app') - -                            # and fix that up with a Frameworks/CEF symlink too -                            with self.prefix(dst=os.path.join( -                                'DullahanHelper.app', 'Contents', 'Frameworks')): -                                # from Dullahan Helper.app/Contents/Frameworks/Chromium Embedded -                                # Framework.framework back to -                                # SLPlugin.app/Contents/Frameworks/Chromium Embedded Framework.framework -                                # Since SLPlugin_framework is itself a -                                # symlink, don't let relsymlinkf() resolve -- -                                # explicitly call relpath(symlink=True) and -                                # create that symlink here. -                                DullahanHelper_framework = \ -                                    self.symlinkf(self.relpath(SLPlugin_framework, symlink=True), -                                                  catch=False) - -                            # change_command includes install_name_tool, the -                            # -change subcommand and the old framework rpath -                            # stamped into the executable. To use it with -                            # run_command(), we must still append the new -                            # framework path and the pathname of the -                            # executable to change. -                            change_command = [ -                                'install_name_tool', '-change', -                                '@rpath/Frameworks/Chromium Embedded Framework.framework/Chromium Embedded Framework'] - -                            with self.prefix(dst=os.path.join( -                                'DullahanHelper.app', 'Contents', 'MacOS')): -                                # Now self.get_dst_prefix() is, at runtime, -                                # @executable_path. Locate the helper app -                                # framework (which is a symlink) from here. -                                newpath = os.path.join( -                                    '@executable_path', -                                    self.relpath(DullahanHelper_framework, symlink=True), -                                    frameworkname) -                                # and restamp the DullahanHelper executable -                                self.run_command( -                                    change_command + -                                    [newpath, self.dst_path_of('DullahanHelper')]) - -                        # SLPlugin plugins -                        with self.prefix(dst="llplugin"): -                            dylibexecutable = 'media_plugin_cef.dylib' -                            self.path2basename("../media_plugins/cef/" + self.args['configuration'], -                                               dylibexecutable) - -                            # Do this install_name_tool *after* media plugin is copied over. -                            # Locate the framework lib executable -- relative to -                            # SLPlugin.app/Contents/MacOS, which will be our -                            # @executable_path at runtime! -                            newpath = os.path.join( -                                '@executable_path', -                                self.relpath(SLPlugin_framework, executable_path["SLPlugin.app"], -                                             symlink=True), -                                frameworkname) -                            # restamp media_plugin_cef.dylib -                            self.run_command( -                                change_command + -                                [newpath, self.dst_path_of(dylibexecutable)]) +                    self.path("secondlife.icns") + +                # Copy in the updater script and helper modules +                self.path(src=os.path.join(pkgdir, 'VMP'), dst="updater") + +                with self.prefix(src="", dst=os.path.join("updater", "icons")): +                    self.path2basename(self.icon_path(), "secondlife.ico") +                    with self.prefix(src="vmp_icons", dst=""): +                        self.path("*.png") +                        self.path("*.gif") -                            # copy LibVLC plugin itself -                            self.path2basename("../media_plugins/libvlc/" + self.args['configuration'], -                                               "media_plugin_libvlc.dylib") +                with self.prefix(src=relpkgdir, dst=""): +                    self.path("libndofdev.dylib") +                    self.path("libhunspell-1.3.0.dylib")    -                            # copy LibVLC dynamic libraries -                            with self.prefix(src=relpkgdir, dst="lib"): -                                self.path( "libvlc*.dylib*" ) -                                # copy LibVLC plugins folder -                                with self.prefix(src='plugins', dst=""): -                                    self.path( "*.dylib" ) -                                    self.path( "plugins.dat" ) +                with self.prefix(src_dst="cursors_mac"): +                    self.path("*.tif") + +                self.path("licenses-mac.txt", dst="licenses.txt") +                self.path("featuretable_mac.txt") +                self.path("SecondLife.nib") + +                with self.prefix(src=pkgdir,dst=""): +                    self.path("ca-bundle.crt") + +                # Translations +                self.path("English.lproj/language.txt") +                self.replace_in(src="English.lproj/InfoPlist.strings", +                                dst="English.lproj/InfoPlist.strings", +                                searchdict={'%%VERSION%%':'.'.join(self.args['version'])} +                                ) +                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") + +                def path_optional(src, dst): +                    """ +                    For a number of our self.path() calls, not only do we want +                    to deal with the absence of src, we also want to remember +                    which were present. Return either an empty list (absent) +                    or a list containing dst (present). Concatenate these +                    return values to get a list of all libs that are present. +                    """ +                    # This was simple before we started needing to pass +                    # wildcards. Fortunately, self.path() ends up appending a +                    # (source, dest) pair to self.file_list for every expanded +                    # file processed. Remember its size before the call. +                    oldlen = len(self.file_list) +                    self.path(src, dst) +                    # The dest appended to self.file_list has been prepended +                    # with self.get_dst_prefix(). Strip it off again. +                    added = [os.path.relpath(d, self.get_dst_prefix()) +                             for s, d in self.file_list[oldlen:]] +                    if not added: +                        print "Skipping %s" % dst +                    return added + +                # dylibs is a list of all the .dylib files we expect to need +                # in our bundled sub-apps. For each of these we'll create a +                # symlink from sub-app/Contents/Resources to the real .dylib. +                # Need to get the llcommon dll from any of the build directories as well. +                libfile_parent = self.get_dst_prefix() +                libfile = "libllcommon.dylib" +                dylibs = path_optional(self.find_existing_file(os.path.join(os.pardir, +                                                               "llcommon", +                                                               self.args['configuration'], +                                                               libfile), +                                                               os.path.join(relpkgdir, libfile)), +                                       dst=libfile) + +                for libfile in ( +                                "libapr-1.0.dylib", +                                "libaprutil-1.0.dylib", +                                "libcollada14dom.dylib", +                                "libexpat.1.dylib", +                                "libexception_handler.dylib", +                                "libGLOD.dylib", +                                # libnghttp2.dylib is a symlink to +                                # libnghttp2.major.dylib, which is a symlink to +                                # libnghttp2.version.dylib. Get all of them. +                                "libnghttp2.*dylib", +                                ): +                    dylibs += path_optional(os.path.join(relpkgdir, libfile), libfile) + +                # SLVoice and vivox lols, no symlinks needed +                for libfile in ( +                                'libortp.dylib', +                                'libsndfile.dylib', +                                'libvivoxoal.dylib', +                                'libvivoxsdk.dylib', +                                'libvivoxplatform.dylib', +                                'SLVoice', +                                ): +                    self.path2basename(relpkgdir, libfile) + +                # dylibs that vary based on configuration +                if self.args['configuration'].lower() == 'debug': +                    for libfile in ( +                                "libfmodexL.dylib", +                                ): +                        dylibs += path_optional(os.path.join(debpkgdir, libfile), libfile) +                else: +                    for libfile in ( +                                "libfmodex.dylib", +                                ): +                        dylibs += path_optional(os.path.join(relpkgdir, libfile), libfile) + +                # our apps +                executable_path = {} +                for app_bld_dir, app in (("mac_crash_logger", "mac-crash-logger.app"), +                                         # plugin launcher +                                         (os.path.join("llplugin", "slplugin"), "SLPlugin.app"), +                                         ): +                    self.path2basename(os.path.join(os.pardir, +                                                    app_bld_dir, self.args['configuration']), +                                       app) +                    executable_path[app] = \ +                        self.dst_path_of(os.path.join(app, "Contents", "MacOS")) + +                    # our apps dependencies on shared libs +                    # for each app, for each dylib we collected in dylibs, +                    # create a symlink to the real copy of the dylib. +                    with self.prefix(dst=os.path.join(app, "Contents", "Resources")): +                        for libfile in dylibs: +                            self.relsymlinkf(os.path.join(libfile_parent, libfile)) + +                # Dullahan helper apps go inside SLPlugin.app +                with self.prefix(dst=os.path.join( +                    "SLPlugin.app", "Contents", "Frameworks")): + +                    frameworkname = 'Chromium Embedded Framework' + +                    # This code constructs a relative symlink from the +                    # target framework folder back to the real CEF framework. +                    # It needs to be relative so that the symlink still works when +                    # (as is normal) the user moves the app bundle out of the DMG +                    # and into the /Applications folder. Note we pass catch=False, +                    # letting the uncaught exception terminate the process, since +                    # without this symlink, Second Life web media can't possibly work. + +                    # It might seem simpler just to symlink Frameworks back to +                    # the parent of Chromimum Embedded Framework.framework. But +                    # that would create a symlink cycle, which breaks our +                    # packaging step. So make a symlink from Chromium Embedded +                    # Framework.framework to the directory of the same name, which +                    # is NOT an ancestor of the symlink. + +                    # from SLPlugin.app/Contents/Frameworks/Chromium Embedded +                    # Framework.framework back to +                    # $viewer_app/Contents/Frameworks/Chromium Embedded Framework.framework +                    SLPlugin_framework = self.relsymlinkf(CEF_framework, catch=False) + +                    # copy DullahanHelper.app +                    self.path2basename(relpkgdir, 'DullahanHelper.app') + +                    # and fix that up with a Frameworks/CEF symlink too +                    with self.prefix(dst=os.path.join( +                        'DullahanHelper.app', 'Contents', 'Frameworks')): +                        # from Dullahan Helper.app/Contents/Frameworks/Chromium Embedded +                        # Framework.framework back to +                        # SLPlugin.app/Contents/Frameworks/Chromium Embedded Framework.framework +                        # Since SLPlugin_framework is itself a +                        # symlink, don't let relsymlinkf() resolve -- +                        # explicitly call relpath(symlink=True) and +                        # create that symlink here. +                        DullahanHelper_framework = \ +                            self.symlinkf(self.relpath(SLPlugin_framework, symlink=True), +                                          catch=False) + +                    # change_command includes install_name_tool, the +                    # -change subcommand and the old framework rpath +                    # stamped into the executable. To use it with +                    # run_command(), we must still append the new +                    # framework path and the pathname of the +                    # executable to change. +                    change_command = [ +                        'install_name_tool', '-change', +                        '@rpath/Frameworks/Chromium Embedded Framework.framework/Chromium Embedded Framework'] + +                    with self.prefix(dst=os.path.join( +                        'DullahanHelper.app', 'Contents', 'MacOS')): +                        # Now self.get_dst_prefix() is, at runtime, +                        # @executable_path. Locate the helper app +                        # framework (which is a symlink) from here. +                        newpath = os.path.join( +                            '@executable_path', +                            self.relpath(DullahanHelper_framework, symlink=True), +                            frameworkname) +                        # and restamp the DullahanHelper executable +                        self.run_command( +                            change_command + +                            [newpath, self.dst_path_of('DullahanHelper')]) + +                # SLPlugin plugins +                with self.prefix(dst="llplugin"): +                    dylibexecutable = 'media_plugin_cef.dylib' +                    self.path2basename("../media_plugins/cef/" + self.args['configuration'], +                                       dylibexecutable) + +                    # Do this install_name_tool *after* media plugin is copied over. +                    # Locate the framework lib executable -- relative to +                    # SLPlugin.app/Contents/MacOS, which will be our +                    # @executable_path at runtime! +                    newpath = os.path.join( +                        '@executable_path', +                        self.relpath(SLPlugin_framework, executable_path["SLPlugin.app"], +                                     symlink=True), +                        frameworkname) +                    # restamp media_plugin_cef.dylib +                    self.run_command( +                        change_command + +                        [newpath, self.dst_path_of(dylibexecutable)]) + +                    # copy LibVLC plugin itself +                    self.path2basename("../media_plugins/libvlc/" + self.args['configuration'], +                                       "media_plugin_libvlc.dylib") + +                    # copy LibVLC dynamic libraries +                    with self.prefix(src=relpkgdir, dst="lib"): +                        self.path( "libvlc*.dylib*" ) +                        # copy LibVLC plugins folder +                        with self.prefix(src='plugins', dst=""): +                            self.path( "*.dylib" ) +                            self.path( "plugins.dat" )      def package_finish(self):          global CHANNEL_VENDOR_BASE @@ -1416,10 +1361,7 @@ open "%s" --args "$@"                              else:                                  print >> sys.stderr, "Maximum codesign attempts exceeded; giving up"                                  raise -                    self.run_command(['spctl', '-a', '-texec', '-vv', app_in_dmg]) - -            imagename="SecondLife_" + '_'.join(self.args['version']) - +                    self.run_command(['spctl', '-a', '-texec', '-vvvv', app_in_dmg])          finally:              # Unmount the image even if exceptions from any of the above  @@ -1458,29 +1400,25 @@ class LinuxManifest(ViewerManifest):          debpkgdir = os.path.join(pkgdir, "lib", "debug")          self.path("licenses-linux.txt","licenses.txt") -        with self.prefix("linux_tools", dst=""): +        with self.prefix("linux_tools"):              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") -            with self.prefix(src="", dst="etc"): +            with self.prefix(dst="etc"):                  self.path("handle_secondlifeprotocol.sh")                  self.path("register_secondlifeprotocol.sh")                  self.path("refresh_desktop_app_entry.sh")                  self.path("launch_url.sh")              self.path("install.sh") -        with self.prefix(src="", dst="bin"): +        with self.prefix(dst="bin"):              self.path("secondlife-bin","do-not-directly-run-secondlife-bin")              self.path("../linux_crash_logger/linux-crash-logger","linux-crash-logger.bin")              self.path2basename("../llplugin/slplugin", "SLPlugin")               #this copies over the python wrapper script, associated utilities and required libraries, see SL-321, SL-322 and SL-323              with self.prefix(src="../viewer_components/manager", dst=""): -                self.path("SL_Launcher")                  self.path("*.py") -            with self.prefix(src=os.path.join("lib", "python", "llbase"), dst="llbase"): -                self.path("*.py") -                self.path("_cllsd.so")                   # recurses, packaged again          self.path("res-sdl") @@ -1488,9 +1426,9 @@ class LinuxManifest(ViewerManifest):          # Get the icons based on the channel type          icon_path = self.icon_path()          print "DEBUG: icon_path '%s'" % icon_path -        with self.prefix(src=icon_path, dst="") : +        with self.prefix(src=icon_path) :              self.path("secondlife_256.png","secondlife_icon.png") -            with self.prefix(src="",dst="res-sdl") : +            with self.prefix(dst="res-sdl") :                  self.path("secondlife_256.BMP","ll_icon.BMP")          # plugins @@ -1512,7 +1450,7 @@ class LinuxManifest(ViewerManifest):          self.path("featuretable_linux.txt") -        with self.prefix(src=pkgdir,dst=""): +        with self.prefix(src=pkgdir):              self.path("ca-bundle.crt")      def package_finish(self): @@ -1555,7 +1493,7 @@ class LinuxManifest(ViewerManifest):              self.run_command(                  ["find"] +                  [os.path.join(self.get_dst_prefix(), dir) for dir in ('bin', 'lib')] + -                ['-type', 'f', '!', '-name', '*.py', '!', '-name', 'SL_Launcher', +                ['-type', 'f', '!', '-name', '*.py',                   '!', '-name', 'update_install', '-exec', 'strip', '-S', '{}', ';'])  class Linux_i686_Manifest(LinuxManifest): @@ -1568,7 +1506,7 @@ class Linux_i686_Manifest(LinuxManifest):          relpkgdir = os.path.join(pkgdir, "lib", "release")          debpkgdir = os.path.join(pkgdir, "lib", "debug") -        with self.prefix(relpkgdir, dst="lib"): +        with self.prefix(src=relpkgdir, dst="lib"):              self.path("libapr-1.so")              self.path("libapr-1.so.0")              self.path("libapr-1.so.0.4.5") @@ -1654,4 +1592,8 @@ class Linux_x86_64_Manifest(LinuxManifest):  ################################################################  if __name__ == "__main__": -    main() +    extra_arguments = [ +        dict(name='bugsplat', description="""BugSplat database to which to post crashes, +             if BugSplat crash reporting is desired""", default=''), +        ] +    main(extra=extra_arguments) diff --git a/indra/test/test.cpp b/indra/test/test.cpp index bdeaeec970..861ec1d942 100644 --- a/indra/test/test.cpp +++ b/indra/test/test.cpp @@ -534,7 +534,6 @@ int main(int argc, char **argv)  		LLError::setDefaultLevel(LLError::LEVEL_DEBUG);  	}	  	LLError::setFatalFunction(wouldHaveCrashed); -	LLError::setPrintLocation(true);  	std::string test_app_name(argv[0]);  	std::string test_log = test_app_name + ".log";  	LLFile::remove(test_log); diff --git a/indra/viewer_components/login/lllogin.cpp b/indra/viewer_components/login/lllogin.cpp index c767d52c7b..9193d32b49 100644 --- a/indra/viewer_components/login/lllogin.cpp +++ b/indra/viewer_components/login/lllogin.cpp @@ -128,6 +128,15 @@ void LLLogin::Impl::connect(const std::string& uri, const LLSD& login_params)      LL_DEBUGS("LLLogin") << " connected with  uri '" << uri << "', login_params " << login_params << LL_ENDL;	  } +namespace { +// Instantiate this rendezvous point at namespace scope so it's already +// present no matter how early the updater might post to it. +// Use an LLEventMailDrop, which has future-like semantics: regardless of the +// relative order in which post() or listen() are called, it delivers each +// post() event to its listener(s) until one of them consumes that event. +static LLEventMailDrop sSyncPoint("LoginSync"); +} +  void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params)  {      LLSD printable_params = login_params; @@ -219,7 +228,44 @@ void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params)          }          else          { -            sendProgressEvent("offline", "fail.login", mAuthResponse["responses"]); +            // Synchronize here with the updater. We synchronize here rather +            // than in the fail.login handler, which actually examines the +            // response from login.cgi, because here we are definitely in a +            // coroutine and can definitely use suspendUntilBlah(). Whoever's +            // listening for fail.login might not be. + +            // If the reason for login failure is that we must install a +            // required update, we definitely want to pass control to the +            // updater to manage that for us. We'll handle any other login +            // failure ourselves, as usual. We figure that no matter where you +            // are in the world, or what kind of network you're on, we can +            // reasonably expect the Viewer Version Manager to respond more or +            // less as quickly as login.cgi. This synchronization is only +            // intended to smooth out minor races between the two services. +            // But what if the updater crashes? Use a timeout so that +            // eventually we'll tire of waiting for it and carry on as usual. +            // Given the above, it can be a fairly short timeout, at least +            // from a human point of view. + +            // Since sSyncPoint is an LLEventMailDrop, we DEFINITELY want to +            // consume the posted event. +            LLCoros::OverrideConsuming oc(true); +            // Timeout should produce the isUndefined() object passed here. +            LL_DEBUGS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL; +            LLSD updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 10, LLSD()); +            if (updater.isUndefined()) +            { +                LL_WARNS("LLLogin") << "Failed to hear from updater, proceeding with fail.login" +                                    << LL_ENDL; +            } +            else +            { +                LL_DEBUGS("LLLogin") << "Got responses from updater and login.cgi" << LL_ENDL; +            } +            // Let the fail.login handler deal with empty updater response. +            LLSD responses(mAuthResponse["responses"]); +            responses["updater"] = updater; +            sendProgressEvent("offline", "fail.login", responses);          }          return;             // Done!      } @@ -249,10 +295,10 @@ void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params)      // *NOTE: The response from LLXMLRPCListener's Poller::poll method returns an      // llsd with no "responses" node. To make the output from an incomplete login symmetrical       // to success, add a data/message and data/reason fields. -    LLSD error_response; -    error_response["reason"] = mAuthResponse["status"]; -    error_response["errorcode"] = mAuthResponse["errorcode"]; -    error_response["message"] = mAuthResponse["error"]; +    LLSD error_response(LLSDMap +                        ("reason",    mAuthResponse["status"]) +                        ("errorcode", mAuthResponse["errorcode"]) +                        ("message",   mAuthResponse["error"]));      if(mAuthResponse.has("certificate"))      {          error_response["certificate"] = mAuthResponse["certificate"]; diff --git a/indra/win_crash_logger/CMakeLists.txt b/indra/win_crash_logger/CMakeLists.txt index 144d037a31..4fba26ab2f 100644 --- a/indra/win_crash_logger/CMakeLists.txt +++ b/indra/win_crash_logger/CMakeLists.txt @@ -89,7 +89,6 @@ target_link_libraries(windows-crash-logger      ${GOOGLE_PERFTOOLS_LIBRARIES}      user32      gdi32 -    ole32      oleaut32      wininet      Wldap32 | 
