summaryrefslogtreecommitdiff
path: root/indra/newview
diff options
context:
space:
mode:
Diffstat (limited to 'indra/newview')
-rw-r--r--indra/newview/CMakeLists.txt338
-rw-r--r--indra/newview/Info-SecondLife.plist2
-rw-r--r--indra/newview/llappdelegate-objc.mm126
-rw-r--r--indra/newview/llappviewer.cpp36
-rw-r--r--indra/newview/llappviewermacosx-for-objc.h53
-rw-r--r--indra/newview/llappviewermacosx.cpp88
-rw-r--r--indra/newview/llappviewerwin32.cpp155
-rw-r--r--indra/newview/llversioninfo.cpp7
-rw-r--r--indra/newview/llviewerregion.cpp19
-rw-r--r--indra/newview/tests/llversioninfo_test.cpp6
-rwxr-xr-xindra/newview/viewer_manifest.py484
11 files changed, 969 insertions, 345 deletions
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index 2fc722d4c3..e573b927d7 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)
@@ -37,7 +44,6 @@ include(LLUI)
include(LLVFS)
include(LLWindow)
include(LLXML)
-include(Linking)
include(NDOF)
include(NVAPI)
include(OPENAL)
@@ -93,6 +99,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}
@@ -1356,6 +1368,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)
@@ -1370,6 +1390,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
@@ -1395,6 +1421,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")
@@ -1411,6 +1442,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
@@ -1693,6 +1729,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)
@@ -1791,7 +1832,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}
@@ -1800,15 +1841,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
@@ -1826,24 +1868,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
@@ -1866,15 +1893,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
@@ -1905,8 +1933,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
@@ -1981,6 +2009,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.")
@@ -2004,15 +2038,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}
@@ -2026,17 +2061,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}
@@ -2054,8 +2090,10 @@ if (LINUX)
endif (LINUX)
if (DARWIN)
- # These all get set with PROPERTIES
- set(product "Second Life")
+ # 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}")
# 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")
@@ -2063,28 +2101,41 @@ if (DARWIN)
# 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")
+ 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(
@@ -2095,15 +2146,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
@@ -2128,15 +2180,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
@@ -2148,67 +2201,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..8aabd6818b 100644
--- a/indra/newview/Info-SecondLife.plist
+++ b/indra/newview/Info-SecondLife.plist
@@ -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>
diff --git a/indra/newview/llappdelegate-objc.mm b/indra/newview/llappdelegate-objc.mm
index aebae4c434..f55304f30b 100644
--- a/indra/newview/llappdelegate-objc.mm
+++ b/indra/newview/llappdelegate-objc.mm
@@ -25,7 +25,14 @@
*/
#import "llappdelegate-objc.h"
+#if defined(LL_BUGSPLAT)
+@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 +54,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 +205,104 @@
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");
+}
+
+- (BugsplatAttachment *)attachmentForBugsplatStartupManager:(BugsplatStartupManager *)bugsplatStartupManager {
+ std::string logfile = CrashMetadata_instance().logFilePathname;
+ // Still to do:
+ // userSettingsPathname
+ // staticDebugPathname
+ // but the BugsplatMac version 1.0.5 BugsplatStartupManagerDelegate API
+ // doesn't yet provide a way to attach more than one file.
+ NSString *ns_logfile = [NSString stringWithCString:logfile.c_str()
+ encoding:NSUTF8StringEncoding];
+ NSData *data = [NSData dataWithContentsOfFile:ns_logfile];
+
+ // Apologies for the hard-coded log-file basename, but I do not know the
+ // incantation for "$(basename "$logfile")" in this language.
+ BugsplatAttachment *attachment =
+ [[BugsplatAttachment alloc] initWithFilename:@"SecondLife.log"
+ attachmentData:data
+ contentType:@"text/plain"];
+ infos("attachmentForBugsplatStartupManager attaching " + logfile);
+ return attachment;
+}
+
+- (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 189f7c1426..dd82aa735f 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
@@ -2172,6 +2181,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 +3054,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 f0a4a54fbf..0de942d507 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,69 @@ 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"));
+ std::ifstream 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/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 4f0460da29..8b8ce3ca9e 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"
@@ -104,6 +105,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
@@ -249,6 +262,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())
@@ -369,6 +385,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/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..464f7aa3e9 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):
@@ -488,19 +495,19 @@ 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")
-
- #VMP Tkinter icons
- with self.prefix("vmp_icons"):
- self.path("*.png")
- self.path("*.gif")
+ 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")
#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"):
@@ -513,8 +520,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 +587,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,114 +607,116 @@ 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/")
+ 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 and updater from other projects
# tag:"crash-logger" here as a cue to the exporter
@@ -879,15 +899,25 @@ class DarwinManifest(ViewerManifest):
# darwin requires full app bundle packaging even for debugging.
return True
+ 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):
# 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"
+ toplevel_app = self.channel()+".app"
+ toplevel_icon = "secondlife.icns"
launcher_app, launcher_icon = "Second Life Launcher.app", "secondlife.icns"
viewer_app, viewer_icon = "Second Life Viewer.app", "secondlife.icns"
+ # capture the path to the directory containing toplevel_app
+ parentdir = os.path.join(self.get_dst_prefix(), os.pardir)
+
# 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="")
@@ -897,27 +927,27 @@ class DarwinManifest(ViewerManifest):
# -------------------- top-level Second Life.app ---------------------
# top-level Second Life application is only a container
- with self.prefix(src="", dst="Contents"): # everything goes in Contents
+ with self.prefix(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)
+ Info_plist = self.dst_path_of("Info.plist")
+ toplevel_MacOS = self.dst_path_of("MacOS")
# the one file in top-level MacOS directory is the trampoline to
# our nested launcher_app
- with self.prefix(dst="MacOS"):
- toplevel_MacOS = self.get_dst_prefix()
- trampoline = self.put_in_file("""\
+ if not self.is_rearranging():
+ trampoline = ""
+ else:
+ with self.prefix(dst="MacOS"):
+ 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])
+ # 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
@@ -929,10 +959,10 @@ open "%s" --args "$@"
# rather than relsymlinkf().
self.symlinkf(os.path.join("Resources", viewer_app, "Contents", "Frameworks"))
- with self.prefix(src="", dst="Resources"):
+ with self.prefix(dst="Resources"):
# top-level Resources directory should be pretty sparse
# need .icns file referenced by top-level Info.plist
- with self.prefix(src=self.icon_path(), dst="") :
+ with self.prefix(src=self.icon_path()) :
self.path(toplevel_icon)
# ------------------- nested launcher_app --------------------
@@ -952,15 +982,15 @@ open "%s" --args "$@"
#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=""):
+ with self.prefix(src=os.path.join(pkgdir, "VMP")):
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=""):
+ with self.prefix(src=os.path.join(pkgdir, "lib", "python")):
# llbase provides our llrest service layer and llsd decoding
- with self.prefix("llbase"):
+ with self.prefix(src="llbase", dst="llbase"):
# (Why is llbase treated specially here? What
# DON'T we want to copy out of lib/python/llbase?)
self.path("*.py")
@@ -973,86 +1003,111 @@ open "%s" --args "$@"
# launcher_app/Contents/Resources
with self.prefix(dst="Resources"):
- with self.prefix(src=self.icon_path(), dst="") :
+ with self.prefix(src=self.icon_path()) :
self.path(launcher_icon)
with self.prefix(dst="vmp_icons"):
self.path("secondlife.ico")
#VMP Tkinter icons
- with self.prefix("vmp_icons"):
+ with self.prefix(src_dst="vmp_icons"):
self.path("*.png")
self.path("*.gif")
# -------------------- nested viewer_app ---------------------
with self.prefix(dst=os.path.join(viewer_app, "Contents")):
+ # defer Info.plist until after MacOS
+ 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 = self.dst_path_of(f)
+ fromrel = self.relpath(fromwhere, relbase)
+ torel = self.relpath(towhere, relbase)
+ if not self.is_rearranging():
+ print "Not yet moving {} => {}".format(fromrel, torel)
+ else:
+ print "Moving {} => {}".format(fromrel, torel)
+ # now do it, only without relativizing paths
+ os.rename(fromwhere, towhere)
+
+ # If we haven't yet moved executables, find our viewer
+ # executable where it was linked, in toplevel_MacOS.
+ # If we have, find it here.
+ whichdir = here if self.is_rearranging() else toplevel_MacOS
+ # Pick the biggest of the executables as the real viewer.
+ # Make (basename, fullpath) pairs; for each pair,
+ # expand to (size, basename, fullpath) triples; sort
+ # by size; pick the last triple; take the basename and
+ # fullpath from that.
+ _, exename, exepath = \
+ sorted((os.path.getsize(path), name, path)
+ for name, path in
+ ((name, os.path.join(whichdir, name))
+ for name in os.listdir(whichdir)))[-1]
+
+ if self.is_rearranging():
+ # NOTE: the -S argument to strip causes it to keep
+ # enough info for annotated backtraces (i.e. function
+ # names in the crash log). 'strip' with no arguments
+ # yields a slightly smaller binary but makes crash
+ # logs mostly useless. This may be desirable for the
+ # final release. Or not.
+ self.run_command(['strip', '-S', exepath])
+
# 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["CFBundleExecutable"] = exename
Info["CFBundleIconFile"] = viewer_icon
+ bugsplat_db = self.args.get('bugsplat')
+ if bugsplat_db:
+ # 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 viewer_app/Contents/Frameworks.
- # Remember where we parked this car.
- with self.prefix(src="", dst="Frameworks"):
+ with self.prefix(dst="Frameworks"):
+ # CEF framework goes inside viewer_app/Contents/Frameworks.
CEF_framework = "Chromium Embedded Framework.framework"
self.path2basename(relpkgdir, CEF_framework)
+ # Remember where we parked this car.
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')])
+ if self.args.get('bugsplat'):
+ self.path2basename(relpkgdir, "BugsplatMac.framework")
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="") :
+ with self.prefix(src=self.icon_path()) :
self.path(viewer_icon)
- with self.prefix(src=relpkgdir, dst=""):
+ with self.prefix(src=relpkgdir):
self.path("libndofdev.dylib")
self.path("libhunspell-1.3.0.dylib")
- with self.prefix("cursors_mac"):
+ 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=""):
+ with self.prefix(src=pkgdir):
self.path("ca-bundle.crt")
self.path("SecondLife.nib")
@@ -1153,13 +1208,14 @@ open "%s" --args "$@"
# 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)
+ 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"))
@@ -1265,7 +1321,7 @@ open "%s" --args "$@"
with self.prefix(src=relpkgdir, dst="lib"):
self.path( "libvlc*.dylib*" )
# copy LibVLC plugins folder
- with self.prefix(src='plugins', dst=""):
+ with self.prefix(src='plugins'):
self.path( "*.dylib" )
self.path( "plugins.dat" )
@@ -1458,24 +1514,24 @@ 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=""):
+ with self.prefix(src="../viewer_components/manager"):
self.path("SL_Launcher")
self.path("*.py")
with self.prefix(src=os.path.join("lib", "python", "llbase"), dst="llbase"):
@@ -1488,9 +1544,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 +1568,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):
@@ -1568,7 +1624,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 +1710,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)