summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
Diffstat (limited to 'indra')
-rw-r--r--indra/CMakeLists.txt1
-rw-r--r--indra/cmake/CMakeLists.txt1
-rw-r--r--indra/cmake/Copy3rdPartyLibs.cmake14
-rw-r--r--indra/cmake/LLAddBuildTest.cmake1
-rw-r--r--indra/cmake/LLBase.cmake4
-rw-r--r--indra/cmake/Linking.cmake1
-rw-r--r--indra/cmake/Requests.cmake7
-rw-r--r--indra/cmake/Variables.cmake3
-rw-r--r--indra/cmake/bugsplat.cmake25
-rw-r--r--indra/integration_tests/llimage_libtest/CMakeLists.txt2
-rwxr-xr-xindra/lib/python/indra/util/llmanifest.py271
-rw-r--r--indra/linux_crash_logger/CMakeLists.txt2
-rw-r--r--indra/llappearance/llavatarappearance.cpp4
-rw-r--r--indra/llappearance/llavatarappearance.h2
-rw-r--r--indra/llappearance/lllocaltextureobject.cpp1
-rw-r--r--indra/llcharacter/llcharacter.h2
-rw-r--r--indra/llcharacter/llkeyframemotion.cpp14
-rw-r--r--indra/llcommon/CMakeLists.txt5
-rw-r--r--indra/llcommon/llapp.cpp23
-rw-r--r--indra/llcommon/llcoros.h20
-rw-r--r--indra/llcommon/llerror.cpp160
-rw-r--r--indra/llcommon/llerrorcontrol.h22
-rw-r--r--indra/llcommon/llevents.cpp23
-rw-r--r--indra/llcommon/llevents.h20
-rw-r--r--indra/llcommon/llfile.cpp19
-rw-r--r--indra/llcommon/llinitparam.h3
-rw-r--r--indra/llcommon/llleap.cpp51
-rw-r--r--indra/llcommon/llleap.h14
-rw-r--r--indra/llcommon/llpreprocessor.h23
-rw-r--r--indra/llcommon/llprocess.cpp27
-rw-r--r--indra/llcommon/llstring.cpp153
-rw-r--r--indra/llcommon/llstring.h199
-rw-r--r--indra/llcommon/stdtypes.h7
-rw-r--r--indra/llcommon/stringize.h52
-rw-r--r--indra/llcommon/tests/llerror_test.cpp96
-rw-r--r--indra/llcommon/tests/llleap_test.cpp7
-rw-r--r--indra/llcommon/tests/llprocess_test.cpp5
-rw-r--r--indra/llcommon/tests/llsdserialize_test.cpp7
-rw-r--r--indra/llcommon/tests/wrapllerrs.h14
-rw-r--r--indra/llimagej2coj/CMakeLists.txt2
-rw-r--r--indra/llmessage/tests/commtest.h8
-rw-r--r--indra/llmessage/tests/llhttpclient_test.cpp9
-rw-r--r--indra/llplugin/slplugin/CMakeLists.txt2
-rw-r--r--indra/llrender/llfontgl.cpp57
-rw-r--r--indra/llui/llnotificationslistener.cpp15
-rw-r--r--indra/llvfs/lldir.cpp5
-rw-r--r--indra/llvfs/lldir_linux.cpp33
-rw-r--r--indra/llvfs/lldir_mac.cpp4
-rw-r--r--indra/llvfs/lldir_solaris.cpp60
-rw-r--r--indra/llvfs/lldir_win32.cpp111
-rw-r--r--indra/llwindow/llwindowmacosx.cpp10
-rw-r--r--indra/llwindow/llwindowwin32.cpp6
-rw-r--r--indra/llxml/tests/llcontrol_test.cpp39
-rw-r--r--indra/media_plugins/base/CMakeLists.txt2
-rw-r--r--indra/media_plugins/cef/CMakeLists.txt2
-rw-r--r--indra/media_plugins/example/CMakeLists.txt2
-rw-r--r--indra/media_plugins/gstreamer010/CMakeLists.txt2
-rw-r--r--indra/media_plugins/libvlc/CMakeLists.txt2
-rw-r--r--indra/newview/CMakeLists.txt349
-rw-r--r--indra/newview/Info-SecondLife.plist6
-rw-r--r--indra/newview/VIEWER_VERSION.txt2
-rw-r--r--indra/newview/app_settings/settings.xml26
-rw-r--r--indra/newview/installers/windows/installer_template.nsi230
-rw-r--r--indra/newview/installers/windows/lang_da.nsibin7816 -> 11760 bytes
-rwxr-xr-x[-rw-r--r--]indra/newview/installers/windows/lang_de.nsibin9850 -> 13014 bytes
-rw-r--r--indra/newview/installers/windows/lang_en-us.nsibin8734 -> 11488 bytes
-rwxr-xr-x[-rw-r--r--]indra/newview/installers/windows/lang_es.nsibin9872 -> 12744 bytes
-rwxr-xr-x[-rw-r--r--]indra/newview/installers/windows/lang_fr.nsibin10258 -> 13262 bytes
-rwxr-xr-x[-rw-r--r--]indra/newview/installers/windows/lang_it.nsibin9520 -> 12480 bytes
-rwxr-xr-x[-rw-r--r--]indra/newview/installers/windows/lang_ja.nsibin7204 -> 9644 bytes
-rw-r--r--indra/newview/installers/windows/lang_pl.nsibin8136 -> 12080 bytes
-rwxr-xr-x[-rw-r--r--]indra/newview/installers/windows/lang_pt-br.nsibin9864 -> 12892 bytes
-rwxr-xr-x[-rw-r--r--]indra/newview/installers/windows/lang_ru.nsibin9188 -> 12216 bytes
-rwxr-xr-x[-rw-r--r--]indra/newview/installers/windows/lang_tr.nsibin9286 -> 12126 bytes
-rwxr-xr-x[-rw-r--r--]indra/newview/installers/windows/lang_zh.nsibin6846 -> 9082 bytes
-rw-r--r--indra/newview/installers/windows/language_menu.nsibin1448 -> 1464 bytes
-rw-r--r--indra/newview/llappdelegate-objc.mm162
-rw-r--r--indra/newview/llappviewer.cpp91
-rw-r--r--indra/newview/llappviewermacosx-for-objc.h53
-rw-r--r--indra/newview/llappviewermacosx.cpp88
-rw-r--r--indra/newview/llappviewerwin32.cpp157
-rw-r--r--indra/newview/llexternaleditor.cpp7
-rw-r--r--indra/newview/lllogininstance.cpp63
-rw-r--r--indra/newview/lllogininstance.h6
-rw-r--r--indra/newview/llstartup.cpp3
-rw-r--r--indra/newview/llversioninfo.cpp7
-rw-r--r--indra/newview/llviewerregion.cpp19
-rw-r--r--indra/newview/llviewerwindow.cpp3
-rw-r--r--indra/newview/llvoicevivox.cpp9
-rw-r--r--indra/newview/llwebprofile.cpp4
-rw-r--r--indra/newview/skins/default/xui/en/notifications.xml39
-rw-r--r--indra/newview/tests/llversioninfo_test.cpp6
-rwxr-xr-xindra/newview/viewer_manifest.py1040
-rw-r--r--indra/test/test.cpp1
-rw-r--r--indra/viewer_components/login/lllogin.cpp56
-rw-r--r--indra/win_crash_logger/CMakeLists.txt1
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 e92546628e..4f320fd147 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}
@@ -1362,6 +1372,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)
@@ -1376,6 +1394,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
@@ -1401,6 +1425,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")
@@ -1417,6 +1446,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
@@ -1553,7 +1587,6 @@ if (WINDOWS)
kernel32
odbc32
odbccp32
- ole32
oleaut32
shell32
Vfw32
@@ -1699,6 +1732,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)
@@ -1797,7 +1835,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}
@@ -1806,15 +1844,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
@@ -1832,24 +1871,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
@@ -1872,15 +1896,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
@@ -1911,8 +1936,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
@@ -1987,6 +2012,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.")
@@ -2010,15 +2041,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}
@@ -2032,17 +2064,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}
@@ -2060,37 +2093,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(
@@ -2101,15 +2143,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
@@ -2134,15 +2177,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
@@ -2154,67 +2198,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 dfda3e0b4f..6abaeb2f90 100644
--- a/indra/newview/VIEWER_VERSION.txt
+++ b/indra/newview/VIEWER_VERSION.txt
@@ -1 +1 @@
-6.1.0
+6.2.0
diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml
index 3ad8b6cded..6e7ddd0bf4 100644
--- a/indra/newview/app_settings/settings.xml
+++ b/indra/newview/app_settings/settings.xml
@@ -4404,6 +4404,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>
@@ -13587,7 +13598,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>
@@ -14068,17 +14079,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>
@@ -16260,7 +16260,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.nsi
index 83e1a3ea94..f462c82078 100644
--- a/indra/newview/installers/windows/lang_da.nsi
+++ b/indra/newview/installers/windows/lang_da.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/lang_de.nsi b/indra/newview/installers/windows/lang_de.nsi
index 2a868acc89..8bb20476b3 100644..100755
--- a/indra/newview/installers/windows/lang_de.nsi
+++ b/indra/newview/installers/windows/lang_de.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/lang_en-us.nsi b/indra/newview/installers/windows/lang_en-us.nsi
index 00aa47de69..fd4d340816 100644
--- a/indra/newview/installers/windows/lang_en-us.nsi
+++ b/indra/newview/installers/windows/lang_en-us.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/lang_es.nsi b/indra/newview/installers/windows/lang_es.nsi
index 1ecf254ffb..8a81110069 100644..100755
--- a/indra/newview/installers/windows/lang_es.nsi
+++ b/indra/newview/installers/windows/lang_es.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/lang_fr.nsi b/indra/newview/installers/windows/lang_fr.nsi
index bec5835bed..f038c0e419 100644..100755
--- a/indra/newview/installers/windows/lang_fr.nsi
+++ b/indra/newview/installers/windows/lang_fr.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/lang_it.nsi b/indra/newview/installers/windows/lang_it.nsi
index 1d2e150525..bd16d8318f 100644..100755
--- a/indra/newview/installers/windows/lang_it.nsi
+++ b/indra/newview/installers/windows/lang_it.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/lang_ja.nsi b/indra/newview/installers/windows/lang_ja.nsi
index 1bd6526670..71edde1992 100644..100755
--- a/indra/newview/installers/windows/lang_ja.nsi
+++ b/indra/newview/installers/windows/lang_ja.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/lang_pl.nsi b/indra/newview/installers/windows/lang_pl.nsi
index a172f0cdeb..05977847b9 100644
--- a/indra/newview/installers/windows/lang_pl.nsi
+++ b/indra/newview/installers/windows/lang_pl.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/lang_pt-br.nsi b/indra/newview/installers/windows/lang_pt-br.nsi
index 87032fec18..0e7cbeacda 100644..100755
--- a/indra/newview/installers/windows/lang_pt-br.nsi
+++ b/indra/newview/installers/windows/lang_pt-br.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/lang_ru.nsi b/indra/newview/installers/windows/lang_ru.nsi
index 019c66123c..d55aacc971 100644..100755
--- a/indra/newview/installers/windows/lang_ru.nsi
+++ b/indra/newview/installers/windows/lang_ru.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/lang_tr.nsi b/indra/newview/installers/windows/lang_tr.nsi
index 1c4e2c2f48..4746f84482 100644..100755
--- a/indra/newview/installers/windows/lang_tr.nsi
+++ b/indra/newview/installers/windows/lang_tr.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/lang_zh.nsi b/indra/newview/installers/windows/lang_zh.nsi
index 355e01a333..397bd0ac81 100644..100755
--- a/indra/newview/installers/windows/lang_zh.nsi
+++ b/indra/newview/installers/windows/lang_zh.nsi
Binary files differ
diff --git a/indra/newview/installers/windows/language_menu.nsi b/indra/newview/installers/windows/language_menu.nsi
index 08ad42532f..2f426a0f47 100644
--- a/indra/newview/installers/windows/language_menu.nsi
+++ b/indra/newview/installers/windows/language_menu.nsi
Binary files differ
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 4374caacdf..d3577d50a1 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -707,6 +707,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()
@@ -781,13 +797,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
@@ -876,11 +885,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 );
@@ -1083,26 +1087,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())
{
@@ -1150,6 +1134,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"));
@@ -1693,7 +1705,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;
@@ -2172,6 +2184,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);
}
@@ -3039,14 +3057,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 37aaece5d6..243ba00457 100644
--- a/indra/newview/llstartup.cpp
+++ b/indra/newview/llstartup.cpp
@@ -480,8 +480,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 3d7d68a62a..4d9288f152 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"
@@ -105,6 +106,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
@@ -252,6 +265,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())
@@ -375,6 +391,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 e53ccd7b8d..43e803fe7a 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 a0bb56a7e4..6e3d157624 100644
--- a/indra/newview/skins/default/xui/en/notifications.xml
+++ b/indra/newview/skins/default/xui/en/notifications.xml
@@ -3907,7 +3907,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
@@ -3917,6 +3916,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