mirror of
https://github.com/FirestormViewer/phoenix-firestorm.git
synced 2026-08-14 00:48:30 +00:00
Release/26.1.1 (#5530)
* Integrate Velopack installer and update framework * Add Velopack update support for macOS and VVM integration * Update Velopack version and dependencies * Improve Velopack packaging for macOS * #5346 Uninstall older non-velopack viewer (#5363) * #5335 Fix silent uninstall asking about registry * #5346 Uninstall older non-velopack viewer * Use runtime viewer exe name, handle Velopack URL * Velopack download failure diagnostic (#5520) * Velopack download failure diagnostic * Fix up velopack downloading updates. Handle updates internally then hand them off to velopack. (#5524) * More velopack changes. Should download updates properly now. * Don't include NSI files * Restore optional updates, refine viewer restart behavior. (#5527) * Add support for optional updates. * Don't restart the viewer after the update unless it was optional. * Setup UpdaterServiceSetting with velopack properly. * Refine the restart behavior a bit - readd the old "the viewer must update" UX. * If the update is still downloading, close should just reopen the downloading dialog. --------- Co-authored-by: Jonathan "Geenz" Goodman <geenz@lindenlab.com> * Remove SLVersionChecker from the viewer with velopack. (#5528) * Remove SLVersionChecker updater integration * Ensure that the portable install has the correct version number. * Don't produce shortcuts with VPK - we do this with our post install. * Bump viewer version from 26.1.0 to 26.1.1 * Potential fix for uninstaller not being functional. * Fix for UpdaterServiceSetting being ignored. * Filter for release channel when generating shortcuts. * Add some more logging for icons on Windows builds. * More VPK logging. * Move velopack packaging in CI to the sign and package step. * Enable velopack downgrade and skip older updates * Move the version required checking into velopack's checks. * Potential fix for downgrade prompts. * Make sure our macOS flow mirrors Windows. * Make sure to use the dev version of the mac sign and package. * p#553 Only one of two uninstallers displayed * #5346 Don't force user to shutdown velopack build for NSIS uninstall * #5346 Ignore option for the uninstall dialog * #5346 Fix early exit crash * #5346 Properly reset version flag. * Add some autodetect logic on macOS. * p#564 Clear legacy links * p#553 Handle uninstall records * p#549 Permit testing release notes on a test build * p#564 Remake nsis to velopack update flow * p#564 Remake nsis to velopack update flow #2 * p#564 Fix incorrect value type * p#553 Clear velopack's own registry entry in favor of a custom one * #5346 Resolve duplicated window class name * Bump to 2.1.0 of sign and package. --------- Co-authored-by: Andrey Kleshchev <117672381+akleshchev@users.noreply.github.com>
This commit is contained in:
co-authored by
Andrey Kleshchev
parent
18db816ef7
commit
5c500ccf40
@@ -2,6 +2,14 @@ name: Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
installer_type:
|
||||
description: 'Windows installer type'
|
||||
type: choice
|
||||
options:
|
||||
- velopack
|
||||
- nsis
|
||||
default: 'velopack'
|
||||
pull_request:
|
||||
push:
|
||||
branches: ["main", "release/*", "project/*"]
|
||||
@@ -53,6 +61,20 @@ jobs:
|
||||
relnotes: ${{ steps.which-branch.outputs.relnotes }}
|
||||
imagename: ${{ steps.build.outputs.imagename }}
|
||||
configuration: ${{ matrix.configuration }}
|
||||
# Windows Velopack outputs (passed to sign-pkg-windows)
|
||||
velopack_pack_id: ${{ steps.build.outputs.velopack_pack_id }}
|
||||
velopack_pack_version: ${{ steps.build.outputs.velopack_pack_version }}
|
||||
velopack_pack_title: ${{ steps.build.outputs.velopack_pack_title }}
|
||||
velopack_main_exe: ${{ steps.build.outputs.velopack_main_exe }}
|
||||
velopack_exclude: ${{ steps.build.outputs.velopack_exclude }}
|
||||
velopack_icon: ${{ steps.build.outputs.velopack_icon }}
|
||||
velopack_installer_base: ${{ steps.build.outputs.velopack_installer_base }}
|
||||
# macOS Velopack outputs (passed to sign-pkg-mac)
|
||||
velopack_mac_pack_id: ${{ steps.build.outputs.velopack_mac_pack_id }}
|
||||
velopack_mac_pack_version: ${{ steps.build.outputs.velopack_mac_pack_version }}
|
||||
velopack_mac_pack_title: ${{ steps.build.outputs.velopack_mac_pack_title }}
|
||||
velopack_mac_main_exe: ${{ steps.build.outputs.velopack_mac_main_exe }}
|
||||
velopack_mac_bundle_id: ${{ steps.build.outputs.velopack_mac_bundle_id }}
|
||||
env:
|
||||
AUTOBUILD_ADDRSIZE: 64
|
||||
AUTOBUILD_BUILD_ID: ${{ github.run_id }}
|
||||
@@ -84,6 +106,8 @@ jobs:
|
||||
# Only set variants to the one configuration: don't let build.sh loop
|
||||
# over variants, let GitHub distribute variants over multiple hosts.
|
||||
variants: ${{ matrix.configuration }}
|
||||
# Pass USE_VELOPACK to CMake when using Velopack installer (default) - Windows and macOS
|
||||
autobuild_configure_parameters: ${{ (contains(matrix.runner, 'windows') || contains(matrix.runner, 'macos')) && (github.event.inputs.installer_type || 'velopack') == 'velopack' && '-- -DUSE_VELOPACK:BOOL=ON' || '' }}
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v5
|
||||
@@ -126,6 +150,17 @@ jobs:
|
||||
with:
|
||||
token: ${{ github.token }}
|
||||
|
||||
- name: Setup .NET for Velopack
|
||||
if: (runner.os == 'Windows' || runner.os == 'macOS') && (github.event.inputs.installer_type || 'velopack') == 'velopack'
|
||||
uses: actions/setup-dotnet@v4
|
||||
with:
|
||||
dotnet-version: '9.0.x'
|
||||
|
||||
- name: Install Velopack CLI
|
||||
if: (runner.os == 'Windows' || runner.os == 'macOS') && (github.event.inputs.installer_type || 'velopack') == 'velopack'
|
||||
shell: bash
|
||||
run: dotnet tool install -g vpk
|
||||
|
||||
- name: Build
|
||||
id: build
|
||||
shell: bash
|
||||
@@ -310,13 +345,21 @@ jobs:
|
||||
steps:
|
||||
- name: Sign and package Windows viewer
|
||||
if: env.AZURE_KEY_VAULT_URI && env.AZURE_CERT_NAME && env.AZURE_CLIENT_ID && env.AZURE_CLIENT_SECRET && env.AZURE_TENANT_ID
|
||||
uses: secondlife/viewer-build-util/sign-pkg-windows@v2.0.4
|
||||
uses: secondlife/viewer-build-util/sign-pkg-windows@v2.1.0
|
||||
with:
|
||||
vault_uri: "${{ env.AZURE_KEY_VAULT_URI }}"
|
||||
cert_name: "${{ env.AZURE_CERT_NAME }}"
|
||||
client_id: "${{ env.AZURE_CLIENT_ID }}"
|
||||
client_secret: "${{ env.AZURE_CLIENT_SECRET }}"
|
||||
tenant_id: "${{ env.AZURE_TENANT_ID }}"
|
||||
installer_type: "${{ github.event.inputs.installer_type || 'velopack' }}"
|
||||
velopack_pack_id: "${{ needs.build.outputs.velopack_pack_id }}"
|
||||
velopack_pack_version: "${{ needs.build.outputs.velopack_pack_version }}"
|
||||
velopack_pack_title: "${{ needs.build.outputs.velopack_pack_title }}"
|
||||
velopack_main_exe: "${{ needs.build.outputs.velopack_main_exe }}"
|
||||
velopack_exclude: "${{ needs.build.outputs.velopack_exclude }}"
|
||||
velopack_icon: "${{ needs.build.outputs.velopack_icon }}"
|
||||
velopack_installer_base: "${{ needs.build.outputs.velopack_installer_base }}"
|
||||
|
||||
sign-and-package-mac:
|
||||
env:
|
||||
@@ -349,7 +392,7 @@ jobs:
|
||||
|
||||
- name: Sign and package Mac viewer
|
||||
if: env.SIGNING_CERT_MACOS && env.SIGNING_CERT_MACOS_IDENTITY && env.SIGNING_CERT_MACOS_PASSWORD && steps.note-creds.outputs.note_user && steps.note-creds.outputs.note_pass && steps.note-creds.outputs.note_team
|
||||
uses: secondlife/viewer-build-util/sign-pkg-mac@v2
|
||||
uses: secondlife/viewer-build-util/sign-pkg-mac@v2.1.0
|
||||
with:
|
||||
channel: ${{ needs.build.outputs.viewer_channel }}
|
||||
imagename: ${{ needs.build.outputs.imagename }}
|
||||
@@ -359,6 +402,11 @@ jobs:
|
||||
note_user: ${{ steps.note-creds.outputs.note_user }}
|
||||
note_pass: ${{ steps.note-creds.outputs.note_pass }}
|
||||
note_team: ${{ steps.note-creds.outputs.note_team }}
|
||||
velopack_pack_id: "${{ needs.build.outputs.velopack_mac_pack_id }}"
|
||||
velopack_pack_version: "${{ needs.build.outputs.velopack_mac_pack_version }}"
|
||||
velopack_pack_title: "${{ needs.build.outputs.velopack_mac_pack_title }}"
|
||||
velopack_main_exe: "${{ needs.build.outputs.velopack_mac_main_exe }}"
|
||||
velopack_bundle_id: "${{ needs.build.outputs.velopack_mac_bundle_id }}"
|
||||
|
||||
post-windows-symbols:
|
||||
env:
|
||||
@@ -439,6 +487,10 @@ jobs:
|
||||
with:
|
||||
pattern: "*-metadata"
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
with:
|
||||
pattern: "*-releases"
|
||||
|
||||
- name: Rename metadata
|
||||
run: |
|
||||
cp Windows-metadata/autobuild-package.xml Windows-autobuild-package.xml
|
||||
@@ -464,12 +516,14 @@ jobs:
|
||||
generate_release_notes: true
|
||||
target_commitish: ${{ github.sha }}
|
||||
append_body: true
|
||||
fail_on_unmatched_files: true
|
||||
fail_on_unmatched_files: false
|
||||
files: |
|
||||
macOS-installer/*.dmg
|
||||
Windows-installer/*.exe
|
||||
*-autobuild-package.xml
|
||||
*-viewer_version.txt
|
||||
Windows-releases/*
|
||||
macOS-releases/*
|
||||
|
||||
- name: post release URL
|
||||
run: |
|
||||
|
||||
@@ -21,7 +21,9 @@ on:
|
||||
project:
|
||||
description: "Project Name (used for channel name in project builds, and tag name for all builds)"
|
||||
default: "hippo"
|
||||
# TODO - add an input for selecting another sha to build other than head of branch
|
||||
tag_override:
|
||||
description: "Override the tag name (optional). If the tag already exists, a numeric suffix is appended."
|
||||
required: false
|
||||
|
||||
jobs:
|
||||
tag-release:
|
||||
@@ -34,7 +36,7 @@ jobs:
|
||||
NIGHTLY_DATE=$(date --rfc-3339=date)
|
||||
echo NIGHTLY_DATE=${NIGHTLY_DATE} >> ${GITHUB_ENV}
|
||||
echo TAG_ID="$(echo ${{ github.sha }} | cut -c1-8)-${{ inputs.project || '${NIGHTLY_DATE}' }}" >> ${GITHUB_ENV}
|
||||
- name: Update Tag
|
||||
- name: Create Tag
|
||||
uses: actions/github-script@v8
|
||||
with:
|
||||
# use a real access token instead of GITHUB_TOKEN default.
|
||||
@@ -44,9 +46,27 @@ jobs:
|
||||
# this token will need to be renewed anually in January
|
||||
github-token: ${{ secrets.LL_TAG_RELEASE_TOKEN }}
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: "refs/tags/${{ env.VIEWER_CHANNEL }}#${{ env.TAG_ID }}",
|
||||
sha: context.sha
|
||||
})
|
||||
const override = `${{ inputs.tag_override }}`.trim();
|
||||
const baseTag = override || `${{ env.VIEWER_CHANNEL }}#${{ env.TAG_ID }}`;
|
||||
|
||||
// Try the base tag first, then append -2, -3, etc. if it already exists
|
||||
let tag = baseTag;
|
||||
for (let attempt = 1; ; attempt++) {
|
||||
try {
|
||||
await github.rest.git.createRef({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
ref: `refs/tags/${tag}`,
|
||||
sha: context.sha
|
||||
});
|
||||
core.info(`Created tag: ${tag}`);
|
||||
break;
|
||||
} catch (e) {
|
||||
if (e.status === 422 && attempt < 10) {
|
||||
core.info(`Tag '${tag}' already exists, trying next suffix...`);
|
||||
tag = `${baseTag}-${attempt + 1}`;
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2914,6 +2914,56 @@ Copyright (c) 2012, 2014, 2015, 2016 nghttp2 contributors</string>
|
||||
<key>description</key>
|
||||
<string>Voxelized Hierarchical Approximate Convex Decomposition</string>
|
||||
</map>
|
||||
<key>velopack</key>
|
||||
<map>
|
||||
<key>platforms</key>
|
||||
<map>
|
||||
<key>windows64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>creds</key>
|
||||
<string>github</string>
|
||||
<key>hash</key>
|
||||
<string>91abbc360640b5b2e0a4c001a36ad411a9a42602</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<key>url</key>
|
||||
<string>https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583560</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>windows64</string>
|
||||
</map>
|
||||
<key>darwin64</key>
|
||||
<map>
|
||||
<key>archive</key>
|
||||
<map>
|
||||
<key>creds</key>
|
||||
<string>github</string>
|
||||
<key>hash</key>
|
||||
<string>05563a79bdeb83d66a72ac1e97587dc2a8f64511</string>
|
||||
<key>hash_algorithm</key>
|
||||
<string>sha1</string>
|
||||
<key>url</key>
|
||||
<string>https://api.github.com/repos/secondlife-3p/3p-velopack/releases/assets/380583554</string>
|
||||
</map>
|
||||
<key>name</key>
|
||||
<string>darwin64</string>
|
||||
</map>
|
||||
</map>
|
||||
<key>license</key>
|
||||
<string>MIT</string>
|
||||
<key>license_file</key>
|
||||
<string>LICENSES/velopack.txt</string>
|
||||
<key>copyright</key>
|
||||
<string>Velopack Ltd.</string>
|
||||
<key>version</key>
|
||||
<string>40232ef.23500976684</string>
|
||||
<key>name</key>
|
||||
<string>velopack</string>
|
||||
<key>description</key>
|
||||
<string>Velopack C/C++ Library</string>
|
||||
</map>
|
||||
</map>
|
||||
<key>package_description</key>
|
||||
<map>
|
||||
|
||||
@@ -62,6 +62,7 @@ set(cmake_SOURCE_FILES
|
||||
UI.cmake
|
||||
UnixInstall.cmake
|
||||
Variables.cmake
|
||||
Velopack.cmake
|
||||
VHACD.cmake
|
||||
ViewerMiscLibs.cmake
|
||||
VisualLeakDetector.cmake
|
||||
|
||||
@@ -13,7 +13,7 @@ elseif (WINDOWS)
|
||||
foreach(hive HKEY_CURRENT_USER HKEY_LOCAL_MACHINE)
|
||||
# prefer more recent Python versions to older ones, if multiple versions
|
||||
# are installed
|
||||
foreach(pyver 3.13 3.12 3.11 3.10 3.9 3.8 3.7)
|
||||
foreach(pyver 3.14 3.13 3.12 3.11 3.10 3.9 3.8 3.7)
|
||||
list(APPEND regpaths "[${hive}\\SOFTWARE\\Python\\PythonCore\\${pyver}\\InstallPath]")
|
||||
endforeach()
|
||||
endforeach()
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
# -*- cmake -*-
|
||||
# Velopack installer and update framework integration
|
||||
# https://velopack.io/
|
||||
|
||||
include_guard()
|
||||
|
||||
# USE_VELOPACK controls whether to use Velopack for installer packaging (instead of NSIS/DMG)
|
||||
option(USE_VELOPACK "Use Velopack for installer packaging" OFF)
|
||||
|
||||
if (WINDOWS)
|
||||
include(Prebuilt)
|
||||
use_prebuilt_binary(velopack)
|
||||
|
||||
add_library(ll::velopack INTERFACE IMPORTED)
|
||||
|
||||
target_include_directories(ll::velopack SYSTEM INTERFACE
|
||||
${LIBS_PREBUILT_DIR}/include/velopack
|
||||
)
|
||||
|
||||
target_link_libraries(ll::velopack INTERFACE
|
||||
${ARCH_PREBUILT_DIRS_RELEASE}/velopack_libc.lib
|
||||
)
|
||||
|
||||
# Windows system libraries required by Velopack
|
||||
target_link_libraries(ll::velopack INTERFACE
|
||||
winhttp
|
||||
ole32
|
||||
shell32
|
||||
shlwapi
|
||||
version
|
||||
userenv
|
||||
ws2_32
|
||||
bcrypt
|
||||
ntdll
|
||||
)
|
||||
|
||||
target_compile_definitions(ll::velopack INTERFACE LL_VELOPACK=1)
|
||||
|
||||
elseif (DARWIN)
|
||||
include(Prebuilt)
|
||||
use_prebuilt_binary(velopack)
|
||||
|
||||
add_library(ll::velopack INTERFACE IMPORTED)
|
||||
|
||||
target_include_directories(ll::velopack SYSTEM INTERFACE
|
||||
${LIBS_PREBUILT_DIR}/include/velopack
|
||||
)
|
||||
|
||||
target_link_libraries(ll::velopack INTERFACE
|
||||
${ARCH_PREBUILT_DIRS_RELEASE}/libvelopack_libc.a
|
||||
)
|
||||
|
||||
# macOS system frameworks required by Velopack (Rust static library dependencies)
|
||||
target_link_libraries(ll::velopack INTERFACE
|
||||
"-framework Foundation"
|
||||
"-framework Security"
|
||||
"-framework SystemConfiguration"
|
||||
"-framework AppKit"
|
||||
"-framework CoreFoundation"
|
||||
"-framework CoreServices"
|
||||
"-framework IOKit"
|
||||
"-liconv"
|
||||
"-lresolv"
|
||||
)
|
||||
|
||||
target_compile_definitions(ll::velopack INTERFACE LL_VELOPACK=1)
|
||||
|
||||
endif()
|
||||
@@ -157,7 +157,8 @@ BASE_ARGUMENTS=[
|
||||
for use by a .bat file.""",
|
||||
default=None),
|
||||
dict(name='versionfile',
|
||||
description="""The name of a file containing the full version number."""),
|
||||
description="""The name of a file containing the full version number.""",
|
||||
default=None),
|
||||
]
|
||||
|
||||
def usage(arguments, srctree=""):
|
||||
|
||||
@@ -38,7 +38,8 @@ LL::WorkQueueBase::WorkQueueBase(const std::string& name, bool auto_shutdown)
|
||||
{
|
||||
// Register for "LLApp" events so we can implicitly close() on viewer shutdown
|
||||
std::string listener_name = "WorkQueue:" + getKey();
|
||||
LLEventPumps::instance().obtain("LLApp").listen(
|
||||
LLEventPumps* pump = LLEventPumps::getInstance();
|
||||
pump->obtain("LLApp").listen(
|
||||
listener_name,
|
||||
[this](const LLSD& stat)
|
||||
{
|
||||
@@ -54,14 +55,25 @@ LL::WorkQueueBase::WorkQueueBase(const std::string& name, bool auto_shutdown)
|
||||
|
||||
// Store the listener name so we can unregister in the destructor
|
||||
mListenerName = listener_name;
|
||||
mPumpHandle = pump->getHandle();
|
||||
}
|
||||
}
|
||||
|
||||
LL::WorkQueueBase::~WorkQueueBase()
|
||||
{
|
||||
if (!mListenerName.empty() && !LLEventPumps::wasDeleted())
|
||||
if (!mListenerName.empty() && !mPumpHandle.isDead())
|
||||
{
|
||||
LLEventPumps::instance().obtain("LLApp").stopListening(mListenerName);
|
||||
// Due to shutdown order issues, use handle, not a singleton
|
||||
// and ignore fiber issue.
|
||||
try
|
||||
{
|
||||
LLEventPumps* pump = mPumpHandle.get();
|
||||
pump->obtain("LLApp").stopListening(mListenerName);
|
||||
}
|
||||
catch (const boost::fibers::lock_error&)
|
||||
{
|
||||
// Likely mutex is down, ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
#include "llcoros.h"
|
||||
#include "llexception.h"
|
||||
#include "llhandle.h"
|
||||
#include "llinstancetracker.h"
|
||||
#include "llinstancetrackersubclass.h"
|
||||
#include "threadsafeschedule.h"
|
||||
@@ -22,6 +23,9 @@
|
||||
#include <functional> // std::function
|
||||
#include <string>
|
||||
|
||||
class LLEventPumps;
|
||||
|
||||
|
||||
namespace LL
|
||||
{
|
||||
|
||||
@@ -202,6 +206,8 @@ namespace LL
|
||||
|
||||
// Name used for the LLApp event listener (empty if not registered)
|
||||
std::string mListenerName;
|
||||
// Due to shutdown order issues, store by handle
|
||||
LLHandle<LLEventPumps> mPumpHandle;
|
||||
};
|
||||
|
||||
/*****************************************************************************
|
||||
|
||||
@@ -43,6 +43,7 @@ include(TinyEXR)
|
||||
include(ThreeJS)
|
||||
include(Tracy)
|
||||
include(UI)
|
||||
include(Velopack)
|
||||
include(ViewerMiscLibs)
|
||||
include(ViewerManager)
|
||||
include(VisualLeakDetector)
|
||||
@@ -659,6 +660,7 @@ set(viewer_SOURCE_FILES
|
||||
llurllineeditorctrl.cpp
|
||||
llurlwhitelist.cpp
|
||||
llversioninfo.cpp
|
||||
llvvmquery.cpp
|
||||
llviewchildren.cpp
|
||||
llviewerassetstats.cpp
|
||||
llviewerassetstorage.cpp
|
||||
@@ -1337,6 +1339,7 @@ set(viewer_HEADER_FILES
|
||||
llurllineeditorctrl.h
|
||||
llurlwhitelist.h
|
||||
llversioninfo.h
|
||||
llvvmquery.h
|
||||
llviewchildren.h
|
||||
llviewerassetstats.h
|
||||
llviewerassetstorage.h
|
||||
@@ -1456,6 +1459,8 @@ if (DARWIN)
|
||||
LIST(APPEND viewer_SOURCE_FILES llappviewermacosx-objc.h)
|
||||
LIST(APPEND viewer_SOURCE_FILES llfilepicker_mac.mm)
|
||||
LIST(APPEND viewer_HEADER_FILES llfilepicker_mac.h)
|
||||
LIST(APPEND viewer_SOURCE_FILES llvelopack.cpp)
|
||||
LIST(APPEND viewer_HEADER_FILES llvelopack.h)
|
||||
|
||||
set_source_files_properties(
|
||||
llappviewermacosx-objc.mm
|
||||
@@ -1518,16 +1523,19 @@ if (WINDOWS)
|
||||
|
||||
list(APPEND viewer_SOURCE_FILES
|
||||
llappviewerwin32.cpp
|
||||
llvelopack.cpp
|
||||
llwindebug.cpp
|
||||
)
|
||||
set_source_files_properties(
|
||||
llappviewerwin32.cpp
|
||||
llvelopack.cpp
|
||||
PROPERTIES
|
||||
COMPILE_DEFINITIONS "${VIEWER_CHANNEL_VERSION_DEFINES}"
|
||||
)
|
||||
|
||||
list(APPEND viewer_HEADER_FILES
|
||||
llappviewerwin32.h
|
||||
llvelopack.h
|
||||
llwindebug.h
|
||||
)
|
||||
|
||||
@@ -1941,6 +1949,7 @@ if (WINDOWS)
|
||||
"--discord=${USE_DISCORD}"
|
||||
"--openal=${USE_OPENAL}"
|
||||
"--tracy=${USE_TRACY}"
|
||||
"--velopack=${USE_VELOPACK}"
|
||||
--build=${CMAKE_CURRENT_BINARY_DIR}
|
||||
--buildtype=$<CONFIG>
|
||||
"--channel=${VIEWER_CHANNEL}"
|
||||
@@ -2064,6 +2073,10 @@ if (USE_DISCORD)
|
||||
target_link_libraries(${VIEWER_BINARY_NAME} ll::discord_sdk )
|
||||
endif ()
|
||||
|
||||
if (TARGET ll::velopack)
|
||||
target_link_libraries(${VIEWER_BINARY_NAME} ll::velopack )
|
||||
endif ()
|
||||
|
||||
if( TARGET ll::intel_memops )
|
||||
target_link_libraries(${VIEWER_BINARY_NAME} ll::intel_memops )
|
||||
endif()
|
||||
@@ -2261,9 +2274,11 @@ if (DARWIN)
|
||||
--arch=${ARCH}
|
||||
--artwork=${ARTWORK_DIR}
|
||||
"--bugsplat=${BUGSPLAT_DB}"
|
||||
--bundleid=${MACOSX_BUNDLE_GUI_IDENTIFIER}
|
||||
"--discord=${USE_DISCORD}"
|
||||
"--openal=${USE_OPENAL}"
|
||||
"--tracy=${USE_TRACY}"
|
||||
"--velopack=${USE_VELOPACK}"
|
||||
--build=${CMAKE_CURRENT_BINARY_DIR}
|
||||
--buildtype=$<CONFIG>
|
||||
"--channel=${VIEWER_CHANNEL}"
|
||||
|
||||
@@ -1 +1 @@
|
||||
26.1.0
|
||||
26.1.1
|
||||
|
||||
@@ -4237,7 +4237,17 @@
|
||||
<key>Value</key>
|
||||
<string>0.0.0</string>
|
||||
</map>
|
||||
|
||||
<key>PreviousInstallChecked</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Whether viewer checked previous install on the same channel for NSIS</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<key>Value</key>
|
||||
<integer>0</integer>
|
||||
</map>
|
||||
<key>LimitDragDistance</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
@@ -13057,11 +13067,11 @@
|
||||
<key>UpdaterShowReleaseNotes</key>
|
||||
<map>
|
||||
<key>Comment</key>
|
||||
<string>Enables displaying of the Release notes in a web floater after update.</string>
|
||||
<string>Enables displaying of the Release notes in a web floater after update. 0 - don't show, 1 - show, 2 - show even for test viewers</string>
|
||||
<key>Persist</key>
|
||||
<integer>1</integer>
|
||||
<key>Type</key>
|
||||
<string>Boolean</string>
|
||||
<string>S32</string>
|
||||
<key>Value</key>
|
||||
<integer>1</integer>
|
||||
</map>
|
||||
|
||||
@@ -767,8 +767,21 @@ Function un.UserSettingsFiles
|
||||
|
||||
StrCmp $DO_UNINSTALL_V2 "true" Keep # Don't remove user's settings files on auto upgrade
|
||||
|
||||
# Ask if user wants to keep data files or not
|
||||
MessageBox MB_YESNO|MB_ICONQUESTION $(RemoveDataFilesMB) IDYES Remove IDNO Keep
|
||||
ClearErrors
|
||||
Push $0
|
||||
${GetParameters} $COMMANDLINE
|
||||
${GetOptionsS} $COMMANDLINE "/clrusrfiles" $0
|
||||
# GetOptionsS returns an error if option does not exist, jump past Goto.
|
||||
IfErrors +3 0
|
||||
Pop $0
|
||||
Goto Remove
|
||||
|
||||
Pop $0
|
||||
ClearErrors
|
||||
|
||||
ifSilent Keep 0
|
||||
# Ask if user wants to keep data files or not
|
||||
MessageBox MB_YESNO|MB_ICONQUESTION $(RemoveDataFilesMB) IDYES Remove IDNO Keep
|
||||
|
||||
Remove:
|
||||
Push $0
|
||||
@@ -864,11 +877,25 @@ RMDir "$INSTDIR"
|
||||
IfFileExists "$INSTDIR" FOLDERFOUND NOFOLDER
|
||||
|
||||
FOLDERFOUND:
|
||||
ifSilent NOFOLDER 0
|
||||
MessageBox MB_OK $(DeleteProgramFilesMB) /SD IDOK IDOK NOFOLDER
|
||||
|
||||
NOFOLDER:
|
||||
|
||||
MessageBox MB_YESNO $(DeleteRegistryKeysMB) IDYES DeleteKeys IDNO NoDelete
|
||||
ClearErrors
|
||||
Push $0
|
||||
${GetParameters} $COMMANDLINE
|
||||
${GetOptionsS} $COMMANDLINE "/clearreg" $0
|
||||
# GetOptionsS returns an error if option does not exist, jump past Goto.
|
||||
IfErrors +3 0
|
||||
Pop $0
|
||||
Goto DeleteKeys
|
||||
|
||||
Pop $0
|
||||
ClearErrors
|
||||
|
||||
ifSilent NoDelete 0
|
||||
MessageBox MB_YESNO $(DeleteRegistryKeysMB) IDYES DeleteKeys IDNO NoDelete
|
||||
|
||||
DeleteKeys:
|
||||
DeleteRegKey SHELL_CONTEXT "SOFTWARE\Classes\x-grid-location-info"
|
||||
@@ -912,21 +939,7 @@ Function .onInstSuccess
|
||||
|
||||
Call CheckWindowsServPack # Warn if not on the latest SP before asking to launch.
|
||||
StrCmp $SKIP_AUTORUN "true" +2;
|
||||
# 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'
|
||||
Exec '"$INSTDIR\$VIEWER_EXE" $SHORTCUT_LANG_PARAM'
|
||||
#
|
||||
FunctionEnd
|
||||
|
||||
|
||||
@@ -98,6 +98,11 @@
|
||||
#include "llurlmatch.h"
|
||||
#include "lltextutil.h"
|
||||
#include "lllogininstance.h"
|
||||
#include "llvvmquery.h"
|
||||
|
||||
#if LL_VELOPACK
|
||||
#include "llvelopack.h"
|
||||
#endif
|
||||
#include "llprogressview.h"
|
||||
#include "llvocache.h"
|
||||
#include "lldiskcache.h"
|
||||
@@ -382,9 +387,6 @@ const std::string ERROR_MARKER_FILE_NAME("SecondLife.error_marker");
|
||||
const std::string LOGOUT_MARKER_FILE_NAME("SecondLife.logout_marker");
|
||||
static std::string gLaunchFileOnQuit;
|
||||
|
||||
// Used on Win32 for other apps to identify our window (eg, win_setup)
|
||||
const char* const VIEWER_WINDOW_CLASSNAME = "Second Life";
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
// List of entries from strings.xml to always replace
|
||||
@@ -656,7 +658,6 @@ LLAppViewer::LLAppViewer()
|
||||
mPurgeCacheOnExit(false),
|
||||
mPurgeUserDataOnExit(false),
|
||||
mSecondInstance(false),
|
||||
mUpdaterNotFound(false),
|
||||
mSavedFinalSnapshot(false),
|
||||
mSavePerAccountSettings(false), // don't save settings on logout unless login succeeded.
|
||||
mQuitRequested(false),
|
||||
@@ -1112,68 +1113,17 @@ bool LLAppViewer::init()
|
||||
|
||||
gGLActive = false;
|
||||
|
||||
#if LL_RELEASE_FOR_DOWNLOAD
|
||||
// Skip updater if this is a non-interactive instance
|
||||
//#if LL_RELEASE_FOR_DOWNLOAD
|
||||
// Launch VVM update check
|
||||
if (!gSavedSettings.getBOOL("CmdLineSkipUpdater") && !gNonInteractive)
|
||||
{
|
||||
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;
|
||||
std::string updater_file;
|
||||
#if LL_WINDOWS
|
||||
updater_file = "SLVersionChecker.exe";
|
||||
updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file);
|
||||
#elif LL_DARWIN
|
||||
updater_file = "SLVersionChecker";
|
||||
updater.executable = gDirUtilp->add(gDirUtilp->getAppRODataDir(), "updater", updater_file);
|
||||
#else
|
||||
updater_file = "SLVersionChecker";
|
||||
updater.executable = gDirUtilp->getExpandedFilename(LL_PATH_EXECUTABLE, updater_file);
|
||||
#endif
|
||||
// add LEAP mode command-line argument to whichever of these we selected
|
||||
updater.args.add("leap");
|
||||
// UpdaterServiceSettings
|
||||
if (gSavedSettings.getBOOL("FirstLoginThisInstall"))
|
||||
{
|
||||
// Befor first login, treat this as 'manual' updates,
|
||||
// updater won't install anything, but required updates
|
||||
updater.args.add("0");
|
||||
}
|
||||
else
|
||||
{
|
||||
updater.args.add(stringize(gSavedSettings.getU32("UpdaterServiceSetting")));
|
||||
}
|
||||
// channel
|
||||
updater.args.add(LLVersionInfo::instance().getChannel());
|
||||
// testok
|
||||
updater.args.add(stringize(gSavedSettings.getBOOL("UpdaterWillingToTest")));
|
||||
// ForceAddressSize
|
||||
updater.args.add(stringize(gSavedSettings.getU32("ForceAddressSize")));
|
||||
|
||||
try
|
||||
{
|
||||
// Run the updater. An exception from launching the updater should bother us.
|
||||
LLLeap::create(updater, true);
|
||||
mUpdaterNotFound = false;
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
LLUIString details = LLNotifications::instance().getGlobalString("LLLeapUpdaterFailure");
|
||||
details.setArg("[UPDATER_APP]", updater_file);
|
||||
OSMessageBox(
|
||||
details.getString(),
|
||||
LLStringUtil::null,
|
||||
OSMB_OK);
|
||||
mUpdaterNotFound = true;
|
||||
}
|
||||
initVVMUpdateCheck();
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_WARNS("InitInfo") << "Skipping updater check." << LL_ENDL;
|
||||
}
|
||||
#endif //LL_RELEASE_FOR_DOWNLOAD
|
||||
//#endif //LL_RELEASE_FOR_DOWNLOAD
|
||||
|
||||
{
|
||||
// Iterate over --leap command-line options. But this is a bit tricky: if
|
||||
@@ -1711,6 +1661,16 @@ void LLAppViewer::flushLFSIO()
|
||||
|
||||
bool LLAppViewer::cleanup()
|
||||
{
|
||||
#if LL_VELOPACK
|
||||
// Apply any pending Velopack update before shutdown
|
||||
if (velopack_is_update_pending())
|
||||
{
|
||||
LL_INFOS("AppInit") << "Applying pending Velopack update on shutdown..." << LL_ENDL;
|
||||
velopack_apply_pending_update(velopack_should_restart_after_update());
|
||||
}
|
||||
velopack_cleanup();
|
||||
#endif
|
||||
|
||||
//ditch LLVOAvatarSelf instance
|
||||
gAgentAvatarp = NULL;
|
||||
|
||||
@@ -3147,7 +3107,7 @@ bool LLAppViewer::initWindow()
|
||||
LLViewerWindow::Params window_params;
|
||||
window_params
|
||||
.title(gWindowTitle)
|
||||
.name(VIEWER_WINDOW_CLASSNAME)
|
||||
.name(sWindowClass)
|
||||
.x(gSavedSettings.getS32("WindowX"))
|
||||
.y(gSavedSettings.getS32("WindowY"))
|
||||
.width(gSavedSettings.getU32("WindowWidth"))
|
||||
@@ -3272,16 +3232,6 @@ bool LLAppViewer::initWindow()
|
||||
return true;
|
||||
}
|
||||
|
||||
bool LLAppViewer::isUpdaterMissing()
|
||||
{
|
||||
return mUpdaterNotFound;
|
||||
}
|
||||
|
||||
bool LLAppViewer::waitForUpdater()
|
||||
{
|
||||
return !gSavedSettings.getBOOL("CmdLineSkipUpdater") && !mUpdaterNotFound && !gNonInteractive;
|
||||
}
|
||||
|
||||
void LLAppViewer::writeDebugInfo(bool isStatic)
|
||||
{
|
||||
#if LL_WINDOWS && LL_BUGSPLAT
|
||||
|
||||
@@ -117,9 +117,6 @@ public:
|
||||
bool quitRequested() { return mQuitRequested; }
|
||||
bool logoutRequestSent() { return mLogoutRequestSent; }
|
||||
bool isSecondInstance() { return mSecondInstance; }
|
||||
bool isUpdaterMissing(); // In use by tests
|
||||
bool waitForUpdater();
|
||||
|
||||
void writeDebugInfo(bool isStatic=true);
|
||||
|
||||
void setServerReleaseNotesURL(const std::string& url) { mServerReleaseNotesURL = url; }
|
||||
@@ -287,6 +284,14 @@ protected:
|
||||
|
||||
virtual void sendOutOfDiskSpaceNotification();
|
||||
|
||||
protected:
|
||||
|
||||
// NSIS relies on this to detect if viewer is up.
|
||||
// NSIS's method is somewhat unreliable since window
|
||||
// can close long before cleanup is done.
|
||||
// sendURLToOtherInstance also relies on this to detect if viewer is up.
|
||||
static constexpr const char* sWindowClass = "Second Life";
|
||||
|
||||
private:
|
||||
|
||||
bool doFrame();
|
||||
@@ -327,7 +332,6 @@ private:
|
||||
static LLAppViewer* sInstance;
|
||||
|
||||
bool mSecondInstance; // Is this a second instance of the app?
|
||||
bool mUpdaterNotFound; // True when attempt to start updater failed
|
||||
|
||||
std::string mMarkerFileName;
|
||||
LLAPRFile mMarkerFile; // A file created to indicate the app is running.
|
||||
|
||||
@@ -72,6 +72,11 @@
|
||||
#include <fstream>
|
||||
#include <exception>
|
||||
|
||||
// Velopack installer and update framework
|
||||
#if LL_VELOPACK
|
||||
#include "llvelopack.h"
|
||||
#endif
|
||||
|
||||
// Bugsplat (http://bugsplat.com) crash reporting tool
|
||||
#ifdef LL_BUGSPLAT
|
||||
#include "BugSplat.h"
|
||||
@@ -220,7 +225,6 @@ LONG WINAPI catchallCrashHandler(EXCEPTION_POINTERS * /*ExceptionInfo*/)
|
||||
return 0;
|
||||
}
|
||||
|
||||
const std::string LLAppViewerWin32::sWindowClass = "Second Life";
|
||||
|
||||
/*
|
||||
This function is used to print to the command line a text message
|
||||
@@ -424,6 +428,31 @@ int APIENTRY WINMAIN(HINSTANCE hInstance,
|
||||
PWSTR pCmdLine,
|
||||
int nCmdShow)
|
||||
{
|
||||
#if LL_VELOPACK
|
||||
// Velopack MUST be initialized first - it may handle install/uninstall
|
||||
// commands and exit the process before we do anything else.
|
||||
if (!velopack_initialize())
|
||||
{
|
||||
// Velopack handled the invocation (install/uninstall hook)
|
||||
|
||||
// Drop install related settings
|
||||
gDirUtilp->initAppDirs("SecondLife");
|
||||
|
||||
std::string user_settings_path = gDirUtilp->getExpandedFilename(LL_PATH_USER_SETTINGS, "settings.xml");
|
||||
LLControlGroup settings("global");
|
||||
if (settings.loadFromFile(user_settings_path))
|
||||
{
|
||||
// If user reinstalls or updates, we want to recheck for nsis leftovers.
|
||||
if (settings.controlExists("PreviousInstallChecked"))
|
||||
{
|
||||
settings.setBOOL("PreviousInstallChecked", false);
|
||||
}
|
||||
settings.saveToFile(user_settings_path, true);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Call Tracy first thing to have it allocate memory
|
||||
// https://github.com/wolfpld/tracy/issues/196
|
||||
LL_PROFILER_FRAME_END;
|
||||
@@ -933,7 +962,7 @@ bool LLAppViewerWin32::restoreErrorTrap()
|
||||
bool LLAppViewerWin32::sendURLToOtherInstance(const std::string& url)
|
||||
{
|
||||
wchar_t window_class[256]; /* Flawfinder: ignore */ // Assume max length < 255 chars.
|
||||
mbstowcs(window_class, sWindowClass.c_str(), 255);
|
||||
mbstowcs(window_class, sWindowClass, 255);
|
||||
window_class[255] = 0;
|
||||
// Use the class instead of the window name.
|
||||
HWND other_window = FindWindow(window_class, NULL);
|
||||
|
||||
@@ -59,8 +59,6 @@ protected:
|
||||
|
||||
std::string generateSerialNumber();
|
||||
|
||||
static const std::string sWindowClass;
|
||||
|
||||
private:
|
||||
void disableWinErrorReporting();
|
||||
|
||||
|
||||
@@ -277,11 +277,6 @@ void LLLoginInstance::constructAuthParams(LLPointer<LLCredential> user_credentia
|
||||
mRequestData["params"] = request_params;
|
||||
mRequestData["options"] = requested_options;
|
||||
mRequestData["http_params"] = http_params;
|
||||
#if LL_RELEASE_FOR_DOWNLOAD
|
||||
mRequestData["wait_for_updater"] = LLAppViewer::instance()->waitForUpdater();
|
||||
#else
|
||||
mRequestData["wait_for_updater"] = false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool LLLoginInstance::handleLoginEvent(const LLSD& event)
|
||||
@@ -316,13 +311,6 @@ 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();
|
||||
@@ -385,26 +373,15 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)
|
||||
}
|
||||
else if(reason_response == "update")
|
||||
{
|
||||
// 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'.
|
||||
// login.cgi rejected login and requires an update. Since Velopack
|
||||
// handles updates now, the best we can do here is tell the user
|
||||
// to download the update manually via the release notes URL.
|
||||
std::string login_version = response["message_args"]["VERSION"];
|
||||
std::string vvm_version = updater["VERSION"];
|
||||
std::string relnotes = updater["URL"];
|
||||
LL_WARNS("LLLogin") << "Login failed because an update to version " << login_version << " is required." << LL_ENDL;
|
||||
// vvm_version might be empty because we might not have gotten
|
||||
// SLVersionChecker's LoginSync handshake. But if it IS populated, it
|
||||
// should (!) be the same as the version we got from login.cgi.
|
||||
if ((! vvm_version.empty()) && vvm_version != login_version)
|
||||
{
|
||||
LL_WARNS("LLLogin") << "VVM update version " << vvm_version
|
||||
<< " differs from login version " << login_version
|
||||
<< "; presenting VVM version to match release notes URL"
|
||||
<< LL_ENDL;
|
||||
login_version = vvm_version;
|
||||
}
|
||||
|
||||
// Try to use the release notes URL from the VVM query if available,
|
||||
// otherwise fall back to constructing one from the version.
|
||||
std::string relnotes = LLVersionInfo::instance().getReleaseNotes();
|
||||
if (relnotes.empty() || relnotes.find("://") == std::string::npos)
|
||||
{
|
||||
relnotes = LLTrans::getString("RELEASE_NOTES_BASE_URL");
|
||||
@@ -420,32 +397,11 @@ void LLLoginInstance::handleLoginFailure(const LLSD& event)
|
||||
args["VERSION"] = login_version;
|
||||
args["URL"] = relnotes;
|
||||
|
||||
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));
|
||||
}
|
||||
LLNotificationsUtil::add(
|
||||
"RequiredUpdate",
|
||||
args,
|
||||
LLSD(),
|
||||
boost::bind(&LLLoginInstance::handleLoginDisallowed, this, _1, _2));
|
||||
}
|
||||
else if(reason_response == "mfa_challenge")
|
||||
{
|
||||
@@ -479,19 +435,6 @@ 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();
|
||||
|
||||
@@ -28,8 +28,6 @@
|
||||
#define LL_LLLOGININSTANCE_H
|
||||
|
||||
#include "lleventdispatcher.h"
|
||||
#include "lleventapi.h"
|
||||
#include <memory> // std::shared_ptr
|
||||
#include "llsecapi.h"
|
||||
class LLLogin;
|
||||
class LLEventStream;
|
||||
@@ -72,10 +70,7 @@ public:
|
||||
void saveMFAHash(LLSD const& response);
|
||||
|
||||
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);
|
||||
|
||||
bool handleLoginEvent(const LLSD& event);
|
||||
void handleLoginFailure(const LLSD& event);
|
||||
@@ -83,7 +78,6 @@ private:
|
||||
void handleDisconnect(const LLSD& event);
|
||||
void handleIndeterminate(const LLSD& event);
|
||||
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);
|
||||
void showMFAChallange(const std::string& message);
|
||||
|
||||
@@ -37,6 +37,9 @@
|
||||
|
||||
#include "llappviewer.h"
|
||||
#include "llbutton.h"
|
||||
#if LL_VELOPACK
|
||||
#include "llvelopack.h"
|
||||
#endif
|
||||
#include "llcheckboxctrl.h"
|
||||
#include "llcommandhandler.h" // for secondlife:///app/login/
|
||||
#include "llcombobox.h"
|
||||
@@ -936,6 +939,19 @@ void LLPanelLogin::handleMediaEvent(LLPluginClassMedia* /*self*/, EMediaEvent ev
|
||||
// static
|
||||
void LLPanelLogin::onClickConnect(bool commit_fields)
|
||||
{
|
||||
#if LL_VELOPACK
|
||||
// In theory, you should never be able to get here.
|
||||
// If there's a required update, try as you might you're not supposed to actually close the downloading update dialog.
|
||||
// But just in case...
|
||||
if (velopack_is_required_update_in_progress())
|
||||
{
|
||||
LLSD args;
|
||||
args["VERSION"] = velopack_get_required_update_version();
|
||||
LLNotificationsUtil::add("DownloadingUpdate", args);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (sInstance && sInstance->mCallback)
|
||||
{
|
||||
if (commit_fields)
|
||||
|
||||
@@ -29,6 +29,11 @@
|
||||
#include "llappviewer.h"
|
||||
#include "llstartup.h"
|
||||
|
||||
#if LL_VELOPACK && LL_WINDOWS
|
||||
#include "llvelopack.h"
|
||||
#include <shellapi.h>
|
||||
#endif
|
||||
|
||||
#if LL_WINDOWS
|
||||
# include <process.h> // _spawnl()
|
||||
#else
|
||||
@@ -266,6 +271,7 @@ std::unique_ptr<LLViewerStats::PhaseMap> LLStartUp::sPhases(new LLViewerStats::P
|
||||
|
||||
void login_show();
|
||||
void login_callback(S32 option, void* userdata);
|
||||
void uninstall_nsis_if_required();
|
||||
void show_release_notes_if_required();
|
||||
void show_first_run_dialog();
|
||||
bool first_run_dialog_callback(const LLSD& notification, const LLSD& response);
|
||||
@@ -921,6 +927,7 @@ bool idle_startup()
|
||||
LL_DEBUGS("AppInit") << "PeekMessage processed" << LL_ENDL;
|
||||
#endif
|
||||
do_startup_frame();
|
||||
uninstall_nsis_if_required();
|
||||
timeout.reset();
|
||||
return false;
|
||||
}
|
||||
@@ -2605,6 +2612,67 @@ void release_notes_coro(const std::string url)
|
||||
LLWeb::loadURLInternal(url);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if this is a fresh velopack install and
|
||||
* if uninstallation of old viewer is needed.
|
||||
*/
|
||||
void uninstall_nsis_if_required()
|
||||
{
|
||||
#if LL_VELOPACK && LL_WINDOWS
|
||||
bool checked_for_legacy_install = gSavedSettings.getBOOL("PreviousInstallChecked");
|
||||
if (checked_for_legacy_install)
|
||||
{
|
||||
return;
|
||||
}
|
||||
gSavedSettings.setBOOL("PreviousInstallChecked", true);
|
||||
|
||||
LL_INFOS() << "Looking for previous NSIS installs" << LL_ENDL;
|
||||
|
||||
S32 found_major = 0;
|
||||
S32 found_minor = 0;
|
||||
S32 found_patch = 0;
|
||||
U64 found_build = 0;
|
||||
|
||||
if (!get_nsis_version(found_major, found_minor, found_patch, found_build))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
LLVersionInfo* ver_inst = LLVersionInfo::getInstance();
|
||||
|
||||
if (found_major > ver_inst->getMajor())
|
||||
{
|
||||
LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (found_major == ver_inst->getMajor()
|
||||
&& found_minor > ver_inst->getMinor())
|
||||
{
|
||||
LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
|
||||
if (found_major == ver_inst->getMajor()
|
||||
&& found_minor == ver_inst->getMinor()
|
||||
&& found_patch > ver_inst->getPatch())
|
||||
{
|
||||
LL_INFOS() << "Found installed nsis version that is newer" << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
|
||||
// Assume that nsis is going to be something like x.x.x, while velopack is x.x.(x+1),
|
||||
// so there is no point to check build.
|
||||
LL_INFOS() << "Found NSIS install " << found_major << "." << found_minor << "." << found_patch << "." << found_build << LL_ENDL;
|
||||
|
||||
clear_nsis_links();
|
||||
|
||||
LLSD args;
|
||||
args["VERSION"] = llformat("%d.%d.%d", found_major, found_minor, found_patch);
|
||||
LLNotificationsUtil::add("FoundLegacyNsisInstallation", args);
|
||||
#endif
|
||||
}
|
||||
|
||||
void validate_release_notes_coro(const std::string url)
|
||||
{
|
||||
LLVersionInfo& versionInfo(LLVersionInfo::instance());
|
||||
@@ -2638,15 +2706,24 @@ void show_release_notes_if_required()
|
||||
// below. If viewer release notes stop working, might be because that
|
||||
// LLEventMailDrop got moved out of LLVersionInfo and hasn't yet been
|
||||
// instantiated.
|
||||
if (!release_notes_shown && (LLVersionInfo::instance().getChannelAndVersion() != gLastRunVersion)
|
||||
&& LLVersionInfo::instance().getViewerMaturity() != LLVersionInfo::TEST_VIEWER // don't show Release Notes for the test builds
|
||||
&& gSavedSettings.getBOOL("UpdaterShowReleaseNotes")
|
||||
&& !gSavedSettings.getBOOL("FirstLoginThisInstall"))
|
||||
if (release_notes_shown
|
||||
|| LLVersionInfo::instance().getChannelAndVersion() == gLastRunVersion
|
||||
|| gSavedSettings.getBOOL("FirstLoginThisInstall")) // New users don't need to see release notes
|
||||
{
|
||||
return;
|
||||
}
|
||||
S32 mode = gSavedSettings.getS32("UpdaterShowReleaseNotes");
|
||||
if (mode == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (mode == 2 // Show even for test builds
|
||||
|| LLVersionInfo::instance().getViewerMaturity() != LLVersionInfo::TEST_VIEWER) // don't show Release Notes for the test builds
|
||||
|
||||
{
|
||||
|
||||
#if LL_RELEASE_FOR_DOWNLOAD
|
||||
if (!gSavedSettings.getBOOL("CmdLineSkipUpdater")
|
||||
&& !LLAppViewer::instance()->isUpdaterMissing())
|
||||
if (!gSavedSettings.getBOOL("CmdLineSkipUpdater"))
|
||||
{
|
||||
// Instantiate a "relnotes" listener which assumes any arriving event
|
||||
// is the release notes URL string. Since "relnotes" is an
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* @file llvelopack.h
|
||||
* @brief Velopack installer and update framework integration
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2025, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLVELOPACK_H
|
||||
#define LL_LLVELOPACK_H
|
||||
|
||||
#if LL_VELOPACK
|
||||
|
||||
#include <string>
|
||||
#include <functional>
|
||||
|
||||
bool velopack_initialize();
|
||||
void velopack_check_for_updates(const std::string& required_version, const std::string& relnotes_url);
|
||||
std::string velopack_get_current_version();
|
||||
bool velopack_is_update_pending();
|
||||
bool velopack_is_required_update_in_progress();
|
||||
std::string velopack_get_required_update_version();
|
||||
bool velopack_should_restart_after_update();
|
||||
void velopack_request_restart_after_update();
|
||||
void velopack_apply_pending_update(bool restart = true);
|
||||
void velopack_set_update_url(const std::string& url);
|
||||
void velopack_set_progress_callback(std::function<void(int)> callback);
|
||||
void velopack_cleanup();
|
||||
|
||||
#if LL_WINDOWS
|
||||
void clear_nsis_links();
|
||||
bool get_nsis_version(
|
||||
int& nsis_major,
|
||||
int& nsis_minor,
|
||||
int& nsis_patch,
|
||||
uint64_t& nsis_build);
|
||||
#endif
|
||||
|
||||
#endif // LL_VELOPACK
|
||||
#endif
|
||||
// EOF
|
||||
@@ -54,7 +54,7 @@ LLVersionInfo::LLVersionInfo():
|
||||
mWorkingChannelName(LL_TO_STRING(LL_VIEWER_CHANNEL)),
|
||||
build_configuration(LLBUILD_CONFIG), // set in indra/cmake/BuildVersion.cmake
|
||||
// instantiate an LLEventMailDrop with canonical name to listen for news
|
||||
// from SLVersionChecker
|
||||
// from the Viewer Version Manager
|
||||
mPump{new LLEventMailDrop("relnotes")},
|
||||
// immediately listen on mPump, store arriving URL into mReleaseNotes
|
||||
mStore{new LLStoreListener<std::string>(*mPump, mReleaseNotes)}
|
||||
|
||||
@@ -112,8 +112,8 @@ private:
|
||||
std::string mReleaseNotes;
|
||||
// Store unique_ptrs to the next couple things so we don't have to explain
|
||||
// to every consumer of this header file all the details of each.
|
||||
// mPump is the LLEventMailDrop on which we listen for SLVersionChecker to
|
||||
// post the release-notes URL from the Viewer Version Manager.
|
||||
// mPump is the LLEventMailDrop on which we listen for the
|
||||
// release-notes URL from the Viewer Version Manager.
|
||||
std::unique_ptr<LLEventMailDrop> mPump;
|
||||
// mStore is an adapter that stores the release-notes URL in mReleaseNotes.
|
||||
std::unique_ptr<LLStoreListener<std::string>> mStore;
|
||||
|
||||
@@ -575,6 +575,7 @@ std::string LLGridManager::getGridLoginID()
|
||||
|
||||
std::string LLGridManager::getUpdateServiceURL()
|
||||
{
|
||||
auto env_update_service = LLStringUtil::getoptenv("SL_UPDATE_SERVICE");
|
||||
std::string update_url_base = gSavedSettings.getString("CmdLineUpdateService");;
|
||||
if ( !update_url_base.empty() )
|
||||
{
|
||||
@@ -582,6 +583,13 @@ std::string LLGridManager::getUpdateServiceURL()
|
||||
<< "Update URL base overridden from command line: " << update_url_base
|
||||
<< LL_ENDL;
|
||||
}
|
||||
else if (env_update_service && env_update_service->find("http") != std::string::npos)
|
||||
{
|
||||
update_url_base = *env_update_service;
|
||||
LL_INFOS("UpdaterService", "GridManager")
|
||||
<< "Update URL base overridden from SL_UPDATE_SERVICE environment variable: " << update_url_base
|
||||
<< LL_ENDL;
|
||||
}
|
||||
else if ( mGridList[mGrid].has(GRID_UPDATE_SERVICE_URL) )
|
||||
{
|
||||
update_url_base = mGridList[mGrid][GRID_UPDATE_SERVICE_URL].asString();
|
||||
|
||||
@@ -783,7 +783,11 @@ void send_viewer_stats(bool include_preferences)
|
||||
fail["failed_resends"] = (S32) gMessageSystem->mFailedResendPackets;
|
||||
fail["off_circuit"] = (S32) gMessageSystem->mOffCircuitPackets;
|
||||
fail["invalid"] = (S32) gMessageSystem->mInvalidOnCircuitPackets;
|
||||
fail["missing_updater"] = (S32) LLAppViewer::instance()->isUpdaterMissing();
|
||||
#if LL_VELOPACK
|
||||
fail["missing_updater"] = false;
|
||||
#else
|
||||
fail["missing_updater"] = true;
|
||||
#endif
|
||||
|
||||
LLSD &inventory = body["inventory"];
|
||||
inventory["usable"] = gInventory.isInventoryUsable();
|
||||
|
||||
@@ -0,0 +1,189 @@
|
||||
/**
|
||||
* @file llvvmquery.cpp
|
||||
* @brief Query the Viewer Version Manager (VVM) for update information
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2025, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#include "llviewerprecompiledheaders.h"
|
||||
#include "llvvmquery.h"
|
||||
|
||||
#include "llcorehttputil.h"
|
||||
#include "llcoros.h"
|
||||
#include "llevents.h"
|
||||
#include "llviewernetwork.h"
|
||||
#include "llversioninfo.h"
|
||||
#include "llviewercontrol.h"
|
||||
#include "llhasheduniqueid.h"
|
||||
#include "lluri.h"
|
||||
#include "llsys.h"
|
||||
|
||||
#if LL_VELOPACK
|
||||
#include "llvelopack.h"
|
||||
#endif
|
||||
|
||||
namespace
|
||||
{
|
||||
std::string get_platform_string()
|
||||
{
|
||||
#if LL_WINDOWS
|
||||
return "win64";
|
||||
#elif LL_DARWIN
|
||||
return "mac64";
|
||||
#elif LL_LINUX
|
||||
return "lnx64";
|
||||
#else
|
||||
return "unknown";
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string get_platform_version()
|
||||
{
|
||||
return LLOSInfo::instance().getOSVersionString();
|
||||
}
|
||||
|
||||
std::string get_machine_id()
|
||||
{
|
||||
unsigned char id[MD5HEX_STR_SIZE];
|
||||
if (llHashedUniqueID(id))
|
||||
{
|
||||
return std::string(reinterpret_cast<char*>(id));
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
void query_vvm_coro()
|
||||
{
|
||||
// Get base URL from grid manager
|
||||
std::string base_url = LLGridManager::getInstance()->getUpdateServiceURL();
|
||||
|
||||
// We use this for dev testing when working with VVM and working on the updater. Not advisable to uncomment it.
|
||||
//std::string base_url = "https://update.qa.secondlife.io/update";
|
||||
|
||||
if (base_url.empty())
|
||||
{
|
||||
LL_WARNS("VVM") << "No update service URL configured" << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
|
||||
// Gather parameters for VVM query
|
||||
std::string channel = LLVersionInfo::instance().getChannel();
|
||||
|
||||
// We use this for dev testing when working with VVM and working on the updater. Not advisable to uncomment it.
|
||||
// std::string channel = "QA Target for Velopack";
|
||||
|
||||
std::string version = LLVersionInfo::instance().getVersion();
|
||||
std::string platform = get_platform_string();
|
||||
std::string platform_version = get_platform_version();
|
||||
std::string test_ok = gSavedSettings.getBOOL("UpdaterWillingToTest") ? "testok" : "testno";
|
||||
std::string machine_id = get_machine_id();
|
||||
|
||||
// Build URL: {base}/v1.2/{channel}/{version}/{platform}/{platform_version}/{testok}/{uuid}
|
||||
std::string url = base_url + "/v1.2/" +
|
||||
LLURI::escape(channel) + "/" +
|
||||
LLURI::escape(version) + "/" +
|
||||
platform + "/" +
|
||||
LLURI::escape(platform_version) + "/" +
|
||||
test_ok + "/" +
|
||||
machine_id;
|
||||
|
||||
LL_INFOS("VVM") << "Querying VVM: " << url << LL_ENDL;
|
||||
|
||||
// Make HTTP GET request
|
||||
LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID);
|
||||
LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t adapter =
|
||||
std::make_shared<LLCoreHttpUtil::HttpCoroutineAdapter>("VVMQuery", httpPolicy);
|
||||
LLCore::HttpRequest::ptr_t request = std::make_shared<LLCore::HttpRequest>();
|
||||
|
||||
LLSD result = adapter->getAndSuspend(request, url);
|
||||
|
||||
// Check HTTP status
|
||||
LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS];
|
||||
LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults);
|
||||
|
||||
if (!status)
|
||||
{
|
||||
if (status.getType() == 404)
|
||||
{
|
||||
LL_INFOS("VVM") << "Unmanaged channel, no updates available" << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
LL_WARNS("VVM") << "VVM query failed: " << status.toString() << LL_ENDL;
|
||||
return;
|
||||
}
|
||||
|
||||
// Read whether this update is required or optional
|
||||
bool update_required = result["required"].asBoolean();
|
||||
std::string relnotes = result["more_info"].asString();
|
||||
|
||||
// Extract update URL for current platform
|
||||
LLSD platforms = result["platforms"];
|
||||
if (platforms.has(platform))
|
||||
{
|
||||
std::string update_url = platforms[platform]["url"].asString();
|
||||
#if LL_VELOPACK
|
||||
std::string velopack_url = platforms[platform]["velopack_url"].asString();
|
||||
U32 updater_service = gSavedSettings.getU32("UpdaterServiceSetting");
|
||||
std::string required_version = update_required ? result["version"].asString() : "";
|
||||
// Skip network check if no required version AND user only wants mandatory updates
|
||||
if (!velopack_url.empty() && (update_required || updater_service != 0))
|
||||
{
|
||||
LL_INFOS("VVM") << "Velopack feed URL: " << velopack_url
|
||||
<< " required_version: " << required_version << LL_ENDL;
|
||||
velopack_set_update_url(velopack_url);
|
||||
|
||||
LLCoros::instance().launch("VelopackUpdateCheck",
|
||||
[required_version, relnotes]()
|
||||
{
|
||||
velopack_check_for_updates(required_version, relnotes);
|
||||
});
|
||||
}
|
||||
else if (!velopack_url.empty())
|
||||
{
|
||||
LL_INFOS("VVM") << "Optional update skipped (UpdaterServiceSetting=0)" << LL_ENDL;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
if (!update_url.empty())
|
||||
{
|
||||
LL_INFOS("VVM") << "Update available at: " << update_url << LL_ENDL;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_INFOS("VVM") << "No update available for platform: " << platform << LL_ENDL;
|
||||
}
|
||||
|
||||
// Post release notes URL to the relnotes event pump
|
||||
if (!relnotes.empty())
|
||||
{
|
||||
LL_INFOS("VVM") << "Release notes URL: " << relnotes << LL_ENDL;
|
||||
LLEventPumps::instance().obtain("relnotes").post(relnotes);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void initVVMUpdateCheck()
|
||||
{
|
||||
LL_INFOS("VVM") << "Initializing VVM update check" << LL_ENDL;
|
||||
LLCoros::instance().launch("VVMUpdateCheck", &query_vvm_coro);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
/**
|
||||
* @file llvvmquery.h
|
||||
* @brief Query the Viewer Version Manager (VVM) for update information
|
||||
*
|
||||
* $LicenseInfo:firstyear=2025&license=viewerlgpl$
|
||||
* Second Life Viewer Source Code
|
||||
* Copyright (C) 2025, Linden Research, Inc.
|
||||
*
|
||||
* This library is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU Lesser General Public
|
||||
* License as published by the Free Software Foundation;
|
||||
* version 2.1 of the License only.
|
||||
*
|
||||
* This library is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public
|
||||
* License along with this library; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
|
||||
*
|
||||
* Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA
|
||||
* $/LicenseInfo$
|
||||
*/
|
||||
|
||||
#ifndef LL_LLVVMQUERY_H
|
||||
#define LL_LLVVMQUERY_H
|
||||
|
||||
/**
|
||||
* Initialize the VVM update check.
|
||||
*
|
||||
* This launches a coroutine that queries the Viewer Version Manager (VVM)
|
||||
* to check for available updates. If an update is available, it configures
|
||||
* Velopack with the update URL and initiates the update check/download.
|
||||
*
|
||||
* The release notes URL from the VVM response is posted to the "relnotes"
|
||||
* event pump for display.
|
||||
*/
|
||||
void initVVMUpdateCheck();
|
||||
|
||||
#endif // LL_LLVVMQUERY_H
|
||||
@@ -200,6 +200,16 @@ No tutorial is currently available.
|
||||
yestext="OK"/>
|
||||
</notification>
|
||||
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="FoundLegacyNsisInstallation"
|
||||
type="alertmodal">
|
||||
[APP_NAME] found an installation of an older version [VERSION]. To uninstall the older version, please follow [https://community.secondlife.com/knowledgebase/english/how-to-uninstall-and-reinstall-second-life-r524 this manual].
|
||||
<usetemplate
|
||||
name="okbutton"
|
||||
yestext="OK"/>
|
||||
</notification>
|
||||
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="LoginFailedNoNetwork"
|
||||
@@ -4421,6 +4431,14 @@ Click OK to download and install.
|
||||
yestext="OK"/>
|
||||
</notification>
|
||||
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="DownloadingUpdate"
|
||||
type="alertmodal">
|
||||
Downloading update [VERSION]...
|
||||
The viewer will restart once the download is complete.
|
||||
</notification>
|
||||
|
||||
<notification
|
||||
icon="alertmodal.tga"
|
||||
name="OptionalUpdateReady"
|
||||
|
||||
@@ -223,8 +223,6 @@ bool llHashedUniqueID(unsigned char* id)
|
||||
//-----------------------------------------------------------------------------
|
||||
#include "../llappviewer.h"
|
||||
void LLAppViewer::forceQuit(void) {}
|
||||
bool LLAppViewer::isUpdaterMissing() { return true; }
|
||||
bool LLAppViewer::waitForUpdater() { return false; }
|
||||
LLAppViewer * LLAppViewer::sInstance = 0;
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
@@ -540,18 +540,6 @@ class Windows_x86_64_Manifest(ViewerManifest):
|
||||
'*.bat',
|
||||
'*.tar.xz')))
|
||||
|
||||
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('SLVersionChecker.exe')
|
||||
|
||||
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,
|
||||
'llplugin', 'slplugin', self.args['configuration']),
|
||||
@@ -765,6 +753,126 @@ class Windows_x86_64_Manifest(ViewerManifest):
|
||||
return '\n'.join(result)
|
||||
|
||||
def package_finish(self):
|
||||
# Check if we should use Velopack instead of NSIS
|
||||
# Note: as of 2026.01's release, we will be building with Velopack's one click install.
|
||||
# We maintain the legacy NSIS packaging mainly for TPVs at this point.
|
||||
if self.args.get('velopack', 'OFF') == 'ON':
|
||||
self.velopack_package_finish()
|
||||
return
|
||||
|
||||
# NSIS packaging (legacy)
|
||||
self.nsis_package_finish()
|
||||
|
||||
def velopack_package_finish(self):
|
||||
# packId determines install folder: %LocalAppData%\{packId}
|
||||
# Uses same naming as NSIS INSTNAME for channel separation
|
||||
pack_id = self.app_name_oneword() # "SecondLife", "SecondLifeBeta", etc.
|
||||
# Velopack requires SemVer2. Use major.minor.patch-buildnumber so that
|
||||
# Velopack can distinguish builds and order them correctly.
|
||||
pack_version = '.'.join(self.args['version'][:3])
|
||||
if len(self.args['version']) > 3 and self.args['version'][3]:
|
||||
pack_version += '-' + self.args['version'][3]
|
||||
pack_title = self.app_name() # Display name with spaces
|
||||
pack_dir = self.get_dst_prefix()
|
||||
main_exe = self.final_exe()
|
||||
installer_base = self.installer_base_name()
|
||||
exclude_pattern = r'.*\.pdb|.*\.map|.*\.bat|.*\.exp|.*\.lib|.*\.nsi|.*\.tar\.xz|secondlife-bin\..*|.*_Setup\.exe|.*-Setup\.exe'
|
||||
|
||||
# Channel-specific icon for the Velopack installer.
|
||||
# CMake copies icons/{channel}/secondlife.ico to res/ll_icon.ico at configure time.
|
||||
# Try the CMake-generated copy first, fall back to the source icon.
|
||||
icon_path = os.path.join(self.get_src_prefix(), 'res', 'll_icon.ico')
|
||||
if not os.path.exists(icon_path):
|
||||
icon_path = os.path.join(self.get_src_prefix(), self.icon_path(), 'secondlife.ico')
|
||||
|
||||
# In CI, defer Velopack packaging to the sign step where Azure credentials
|
||||
# are available. Emit metadata as GitHub outputs so the sign step can run
|
||||
# vpk pack with --signTemplate, producing a package with signed executables.
|
||||
if os.getenv('GITHUB_ACTIONS'):
|
||||
# Copy the icon into pack_dir so it's included in the Windows-app artifact
|
||||
icon_filename = ''
|
||||
if os.path.exists(icon_path):
|
||||
icon_filename = os.path.basename(icon_path)
|
||||
icon_dest = os.path.join(pack_dir, icon_filename)
|
||||
shutil.copy2(icon_path, icon_dest)
|
||||
print("Copied icon %s to %s" % (icon_path, icon_dest))
|
||||
else:
|
||||
print("WARNING: Icon not found at %s" % icon_path)
|
||||
|
||||
# Emit metadata for the sign step
|
||||
self.set_github_output('velopack_pack_id', pack_id)
|
||||
self.set_github_output('velopack_pack_version', pack_version)
|
||||
self.set_github_output('velopack_pack_title', pack_title)
|
||||
self.set_github_output('velopack_main_exe', main_exe)
|
||||
self.set_github_output('velopack_icon', icon_filename)
|
||||
self.set_github_output('velopack_installer_base', installer_base)
|
||||
self.set_github_output('velopack_exclude', exclude_pattern)
|
||||
# Set package_file so llmanifest's touched.bat logic doesn't crash
|
||||
self.package_file = installer_base + '_Setup.exe'
|
||||
print("CI mode: Velopack packaging deferred to sign step")
|
||||
return
|
||||
|
||||
# Local builds: run vpk pack directly (unsigned)
|
||||
vpk_args = [
|
||||
'vpk', 'pack',
|
||||
'--packId', pack_id,
|
||||
'--packVersion', pack_version,
|
||||
'--packDir', pack_dir,
|
||||
'--mainExe', main_exe,
|
||||
'--packTitle', pack_title,
|
||||
'--exclude', exclude_pattern,
|
||||
# Suppress Velopack's built-in shortcut creation; we create our own
|
||||
# shortcuts in llvelopack.cpp on_after_install hook instead.
|
||||
'--shortcuts', '',
|
||||
]
|
||||
|
||||
# Add icon — CMake copies the channel-appropriate secondlife.ico to res/ll_icon.ico
|
||||
if os.path.exists(icon_path):
|
||||
print("Using icon: %s" % icon_path)
|
||||
vpk_args.extend(['--icon', icon_path])
|
||||
else:
|
||||
print("WARNING: Icon not found at %s — Setup.exe will have no icon" % icon_path)
|
||||
|
||||
print("Running Velopack packaging: %s" % ' '.join(vpk_args))
|
||||
|
||||
# Run vpk command
|
||||
import subprocess
|
||||
result = subprocess.run(vpk_args, cwd=os.path.dirname(pack_dir), capture_output=True, text=True)
|
||||
if result.stdout:
|
||||
print("vpk stdout: %s" % result.stdout)
|
||||
if result.stderr:
|
||||
print("vpk stderr: %s" % result.stderr)
|
||||
if result.returncode != 0:
|
||||
raise ManifestError("Velopack packaging failed with code %d" % result.returncode)
|
||||
|
||||
# Velopack outputs to a Releases directory
|
||||
releases_dir = os.path.join(os.path.dirname(pack_dir), 'Releases')
|
||||
|
||||
# Move the setup exe INTO pack_dir so it's included in the Windows-app artifact
|
||||
# IMPORTANT: Use hyphen format (-Setup.exe) to avoid the *_Setup.exe exclusion pattern
|
||||
# in viewer_app output (line ~538). The underscore pattern excludes NSIS installers
|
||||
# which are rebuilt during signing, but Velopack installers are created here.
|
||||
# Velopack creates: {packId}-win-Setup.exe
|
||||
velopack_setup = os.path.join(releases_dir, '%s-win-Setup.exe' % pack_id)
|
||||
self.package_file = installer_base + '_Setup.exe'
|
||||
our_setup = os.path.join(pack_dir, self.package_file)
|
||||
if os.path.exists(velopack_setup):
|
||||
shutil.move(velopack_setup, our_setup)
|
||||
print("Moved %s to %s" % (velopack_setup, our_setup))
|
||||
|
||||
# Rename the portable zip to include the version number
|
||||
# Velopack creates: {packId}-win-Portable.zip
|
||||
velopack_portable = os.path.join(releases_dir, '%s-win-Portable.zip' % pack_id)
|
||||
if os.path.exists(velopack_portable):
|
||||
our_portable = os.path.join(releases_dir, installer_base + '_Portable.zip')
|
||||
shutil.move(velopack_portable, our_portable)
|
||||
print("Moved %s to %s" % (velopack_portable, our_portable))
|
||||
|
||||
# Output the Releases directory path for artifact upload (contains nupkg, RELEASES for updates)
|
||||
self.set_github_output('velopack_releases', releases_dir)
|
||||
|
||||
def nsis_package_finish(self):
|
||||
"""Package the viewer using NSIS installer (legacy)"""
|
||||
# a standard map of strings for replacing in the templates
|
||||
substitution_strings = {
|
||||
'version' : '.'.join(self.args['version']),
|
||||
@@ -781,7 +889,7 @@ class Windows_x86_64_Manifest(ViewerManifest):
|
||||
substitution_strings['installer_file'] = installer_file
|
||||
|
||||
version_vars = """
|
||||
!define INSTEXE "SLVersionChecker.exe"
|
||||
!define INSTEXE "%(final_exe)s"
|
||||
!define VERSION "%(version_short)s"
|
||||
!define VERSION_LONG "%(version)s"
|
||||
!define VERSION_DASHES "%(version_dashes)s"
|
||||
@@ -967,15 +1075,6 @@ class Darwin_x86_64_Manifest(ViewerManifest):
|
||||
with self.prefix(src=self.icon_path(), dst="") :
|
||||
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")
|
||||
|
||||
with self.prefix(src_dst="cursors_mac"):
|
||||
self.path("*.tif")
|
||||
|
||||
@@ -1127,6 +1226,123 @@ class Darwin_x86_64_Manifest(ViewerManifest):
|
||||
arcname=self.app_name() + ".app")
|
||||
self.set_github_output_path('viewer_app', tarpath)
|
||||
|
||||
# Generate Velopack update packages if enabled
|
||||
# This creates the nupkg and RELEASES files needed for auto-updates
|
||||
# Distribution is still via DMG, but updates use Velopack
|
||||
if self.args.get('velopack', 'OFF') == 'ON':
|
||||
self.velopack_package_finish()
|
||||
|
||||
def velopack_package_finish(self):
|
||||
"""Generate Velopack update packages for macOS.
|
||||
|
||||
This creates the nupkg and releases.json files needed for auto-updates.
|
||||
Distribution is still via DMG - Velopack only handles the update infrastructure.
|
||||
"""
|
||||
# packId determines install identification - same as Windows for consistency
|
||||
pack_id = self.app_name_oneword() # "SecondLife", "SecondLifeBeta", etc.
|
||||
# Velopack requires SemVer2. Use major.minor.patch-buildnumber so that
|
||||
# Velopack can distinguish builds and order them correctly.
|
||||
pack_version = '.'.join(self.args['version'][:3])
|
||||
if len(self.args['version']) > 3 and self.args['version'][3]:
|
||||
pack_version += '-' + self.args['version'][3]
|
||||
pack_title = self.app_name() # Display name with spaces
|
||||
|
||||
# The .app bundle path (e.g., "/path/to/Second Life Release.app")
|
||||
app_bundle = self.get_dst_prefix()
|
||||
# Bundle ID from args (e.g., "com.secondlife.viewer")
|
||||
bundle_id = self.args.get('bundleid', 'com.secondlife.indra.viewer')
|
||||
|
||||
# Icon path for macOS
|
||||
icon_path = os.path.join(self.get_src_prefix(), self.icon_path(), 'secondlife.icns')
|
||||
|
||||
# The main executable inside Contents/MacOS/ is named after the channel
|
||||
main_exe = self.channel()
|
||||
|
||||
# In CI, defer Velopack packaging to the sign step where code signing
|
||||
# credentials are available. Emit metadata as GitHub outputs so the
|
||||
# sign step can run vpk pack after signing the app bundle.
|
||||
if os.getenv('GITHUB_ACTIONS'):
|
||||
self.set_github_output('velopack_mac_pack_id', pack_id)
|
||||
self.set_github_output('velopack_mac_pack_version', pack_version)
|
||||
self.set_github_output('velopack_mac_pack_title', pack_title)
|
||||
self.set_github_output('velopack_mac_main_exe', main_exe)
|
||||
self.set_github_output('velopack_mac_bundle_id', bundle_id)
|
||||
print("CI mode: macOS Velopack packaging deferred to sign step")
|
||||
return
|
||||
|
||||
# Local builds: run vpk pack directly (unsigned)
|
||||
|
||||
# Parent directory containing the .app bundle - this is where we run vpk from
|
||||
# and where the Releases directory will be created
|
||||
work_dir = os.path.dirname(app_bundle)
|
||||
|
||||
# Output directory for releases - clean it first to avoid version conflicts
|
||||
releases_dir = os.path.join(work_dir, 'Releases')
|
||||
if os.path.exists(releases_dir):
|
||||
print("Cleaning existing Releases directory: %s" % releases_dir)
|
||||
shutil.rmtree(releases_dir)
|
||||
|
||||
# Build vpk command for macOS
|
||||
# See: https://docs.velopack.io/reference/cli/content/vpk-osx
|
||||
vpk_args = [
|
||||
'vpk', 'pack',
|
||||
'--packId', pack_id,
|
||||
'--packVersion', pack_version,
|
||||
'--packDir', app_bundle,
|
||||
'--packTitle', pack_title,
|
||||
'--mainExe', main_exe, # Executable name inside Contents/MacOS/
|
||||
'--bundleId', bundle_id,
|
||||
'--outputDir', releases_dir,
|
||||
'--noInst', # Don't generate .pkg installer - we use DMG for distribution
|
||||
'--verbose', # Show detailed output
|
||||
]
|
||||
|
||||
# Add icon if exists
|
||||
if os.path.exists(icon_path):
|
||||
vpk_args.extend(['--icon', icon_path])
|
||||
|
||||
print("Running Velopack packaging for macOS:")
|
||||
print(" Command: %s" % ' '.join(vpk_args))
|
||||
print(" Working directory: %s" % work_dir)
|
||||
print(" App bundle: %s" % app_bundle)
|
||||
print(" Main executable: %s" % main_exe)
|
||||
|
||||
# Run vpk command
|
||||
result = subprocess.run(vpk_args, cwd=work_dir, capture_output=True, text=True)
|
||||
|
||||
# Always print output for debugging
|
||||
if result.stdout:
|
||||
print("vpk stdout:\n%s" % result.stdout)
|
||||
if result.stderr:
|
||||
print("vpk stderr:\n%s" % result.stderr)
|
||||
|
||||
if result.returncode != 0:
|
||||
raise ManifestError("Velopack packaging failed with code %d" % result.returncode)
|
||||
|
||||
# Verify the Releases directory was created and contains expected files
|
||||
if not os.path.exists(releases_dir):
|
||||
raise ManifestError("Velopack releases directory not found: %s" % releases_dir)
|
||||
|
||||
# List what was created
|
||||
releases_contents = os.listdir(releases_dir)
|
||||
print("Velopack releases directory contents: %s" % releases_contents)
|
||||
|
||||
# Verify we have the expected files (nupkg and releases JSON)
|
||||
nupkg_files = [f for f in releases_contents if f.endswith('.nupkg')]
|
||||
json_files = [f for f in releases_contents if f.endswith('.json')]
|
||||
|
||||
if not nupkg_files:
|
||||
raise ManifestError("No .nupkg files found in releases directory")
|
||||
if not json_files:
|
||||
raise ManifestError("No releases JSON files found in releases directory")
|
||||
|
||||
print("Generated %d nupkg file(s): %s" % (len(nupkg_files), nupkg_files))
|
||||
print("Generated %d JSON file(s): %s" % (len(json_files), json_files))
|
||||
|
||||
# Output the Releases directory path for artifact upload
|
||||
self.set_github_output('velopack_releases', releases_dir)
|
||||
print("Velopack releases directory: %s" % releases_dir)
|
||||
|
||||
|
||||
class LinuxManifest(ViewerManifest):
|
||||
build_data_json_platform = 'lnx'
|
||||
@@ -1324,6 +1540,7 @@ if __name__ == "__main__":
|
||||
dict(name='discord', description="""Indication discord social sdk libraries are needed""", default='OFF'),
|
||||
dict(name='openal', description="""Indication openal libraries are needed""", default='OFF'),
|
||||
dict(name='tracy', description="""Indication tracy profiler is enabled""", default='OFF'),
|
||||
dict(name='velopack', description="""Use Velopack installer instead of NSIS""", default='OFF'),
|
||||
]
|
||||
try:
|
||||
main(extra=extra_arguments)
|
||||
|
||||
@@ -34,7 +34,6 @@
|
||||
|
||||
#include "llcoros.h"
|
||||
#include "llevents.h"
|
||||
#include "lleventfilter.h"
|
||||
#include "lleventcoro.h"
|
||||
#include "llexception.h"
|
||||
#include "stringize.h"
|
||||
@@ -133,16 +132,6 @@ 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 = hidePasswd(login_params);
|
||||
@@ -225,58 +214,7 @@ void LLLogin::Impl::loginCoro(std::string uri, LLSD login_params)
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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);
|
||||
LLSD responses(mAuthResponse["responses"]);
|
||||
LLSD updater;
|
||||
|
||||
if (printable_params["wait_for_updater"].asBoolean())
|
||||
{
|
||||
std::string reason_response = responses["data"]["reason"].asString();
|
||||
// Timeout should produce the isUndefined() object passed here.
|
||||
if (reason_response == "update")
|
||||
{
|
||||
LL_INFOS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL;
|
||||
updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 10, LLSD());
|
||||
}
|
||||
else
|
||||
{
|
||||
LL_DEBUGS("LLLogin") << "Login failure, waiting for sync from updater" << LL_ENDL;
|
||||
updater = llcoro::suspendUntilEventOnWithTimeout(sSyncPoint, 3, 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.
|
||||
responses["updater"] = updater;
|
||||
sendProgressEvent("offline", "fail.login", responses);
|
||||
}
|
||||
return; // Done!
|
||||
|
||||
Reference in New Issue
Block a user