Merge remote-tracking branch 'origin/main' into DRTVWR-559

master
Brad Linden 2023-10-25 13:22:13 -07:00
commit 673b3309dd
48 changed files with 2635 additions and 2462 deletions

18
.github/release.yaml vendored Normal file
View File

@ -0,0 +1,18 @@
changelog:
exclude:
labels:
- ignore-for-release
authors:
- dependabot
categories:
- title: Breaking Changes 🛠
labels:
- semver-major
- breaking-change
- title: New Features 🎉
labels:
- semver-minor
- enhancement
- title: Other Changes
labels:
- '*'

View File

@ -4,7 +4,7 @@ on:
workflow_dispatch:
pull_request:
push:
branches: [main, contribute]
branches: ["*"]
tags: ["*"]
jobs:
@ -12,47 +12,84 @@ jobs:
strategy:
matrix:
runner: [windows-large, macos-12-xl]
configuration: [ReleaseOS]
addrsize: [64]
configuration: [Release, ReleaseOS]
python-version: ["3.11"]
include:
- runner: macos-12-xl
developer_dir: "/Applications/Xcode_14.0.1.app/Contents/Developer"
exclude:
- runner: macos-12-xl
runs-on: ${{ matrix.runner }}
outputs:
viewer_channel: ${{ steps.build.outputs.viewer_channel }}
viewer_version: ${{ steps.build.outputs.viewer_version }}
imagename: ${{ steps.build.outputs.imagename }}
env:
AUTOBUILD_ADDRSIZE: 64
AUTOBUILD_BUILD_ID: ${{ github.run_id }}
AUTOBUILD_CONFIGURATION: ${{ matrix.configuration }}
AUTOBUILD_ADDRSIZE: ${{ matrix.addrsize }}
# authorizes fetching private constituent packages
AUTOBUILD_GITHUB_TOKEN: ${{ secrets.SHARED_AUTOBUILD_GITHUB_TOKEN }}
AUTOBUILD_INSTALLABLE_CACHE: ${{ github.workspace }}/.autobuild-installables
AUTOBUILD_VARIABLES_FILE: ${{ github.workspace }}/.build-variables/variables
AUTOBUILD_VSVER: "170" # vs2k22
AUTOBUILD_VSVER: "170"
DEVELOPER_DIR: ${{ matrix.developer_dir }}
LOGFAIL: debug # Show details when tests fail
# Ensure that Linden viewer builds engage Bugsplat.
BUGSPLAT_DB: ${{ matrix.configuration != 'ReleaseOS' && 'SecondLife_Viewer_2018' || '' }}
BUGSPLAT_PASS: ${{ secrets.BUGSPLAT_PASS }}
BUGSPLAT_USER: ${{ secrets.BUGSPLAT_USER }}
build_coverity: false
build_log_dir: ${{ github.workspace }}/.logs
build_viewer: true
BUILDSCRIPTS_SHARED: ${{ github.workspace }}/.shared
# extracted and committed to viewer repo
BUILDSCRIPTS_SUPPORT_FUNCTIONS: ${{ github.workspace }}/buildscripts_support_functions
GIT_REF: ${{ github.head_ref || github.ref }}
LL_SKIP_REQUIRE_SYSROOT: 1
# Setting this variable directs Linden's TUT test driver code to capture
# test-program log output at the specified level, but to display it only if
# the individual test fails.
LOGFAIL: DEBUG
master_message_template_checkout: ${{ github.workspace }}/.master-message-template
# 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 }}
steps:
- name: Checkout code
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha || github.sha }}
- name: Setup python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Checkout build variables
uses: actions/checkout@v3
uses: actions/checkout@v4
with:
repository: secondlife/build-variables
ref: viewer
path: .build-variables
- name: Checkout master-message-template
uses: actions/checkout@v4
with:
repository: secondlife/master-message-template
path: .master-message-template
- name: Install autobuild and python dependencies
run: pip3 install autobuild llbase
run: pip3 install autobuild llsd
- name: Cache autobuild packages
uses: actions/cache@v3
id: cache-installables
with:
path: .autobuild-installables
key: ${{ runner.os }}-${{ matrix.addrsize }}-${{ matrix.configuration }}-${{ hashFiles('autobuild.xml') }}
key: ${{ runner.os }}-64-${{ matrix.configuration }}-${{ hashFiles('autobuild.xml') }}
restore-keys: |
${{ runner.os }}-${{ matrix.addrsize }}-${{ matrix.configuration }}-
${{ runner.os }}-${{ matrix.addrsize }}-
${{ runner.os }}-64-${{ matrix.configuration }}-
${{ runner.os }}-64-
- name: Install windows dependencies
if: runner.os == 'Windows'
@ -64,31 +101,266 @@ jobs:
env:
RUNNER_OS: ${{ runner.os }}
run: |
# set up things the viewer's build.sh script expects
set -x
mkdir -p "$build_log_dir"
mkdir -p "$BUILDSCRIPTS_SHARED/packages/lib/python"
source "$BUILDSCRIPTS_SUPPORT_FUNCTIONS"
if [[ "$OSTYPE" =~ cygwin|msys ]]
then
native_path() { cygpath --windows "$1"; }
shell_path() { cygpath --unix "$1"; }
else
native_path() { echo "$1"; }
shell_path() { echo "$1"; }
fi
finalize()
{
case "$1" in
true|0)
record_success "Build Succeeded"
;;
*)
record_failure "Build Failed with $1"
;;
esac
}
initialize_build()
{
echo "initialize_build"
}
initialize_version()
{
export revision="$AUTOBUILD_BUILD_ID"
}
python_cmd()
{
if [[ "x${1:0:1}" == "x-" ]] # -m, -c, etc.
then # if $1 is a switch, don't try to twiddle paths
"$(shell_path "$PYTHON_COMMAND")" "$@"
elif [[ "$(basename "$1")" == "codeticket.py" ]]
then # ignore any attempt to contact codeticket
echo "## $@"
else # running a script at an explicit path: fix path for Python
local script="$1"
shift
"$(shell_path "$PYTHON_COMMAND")" "$(native_path "$script")" "$@"
fi
}
repo_branch()
{
git -C "$1" branch | grep '^* ' | cut -c 3-
}
record_dependencies_graph()
{
echo "TODO: generate and post dependency graph"
}
# Since we're not uploading to codeticket, DO NOT sleep for minutes.
sleep()
{
echo "Not sleeping for $1 seconds"
}
export -f native_path shell_path finalize initialize_build initialize_version
export -f python_cmd repo_branch record_dependencies_graph sleep
## Useful for diagnosing Windows LLProcess/LLLeap test failures
##export APR_LOG="${RUNNER_TEMP}/apr.log"
export arch=$(uname | cut -b-6)
# Surprise! GH Windows runner's MINGW6 is a $arch value we've never
# seen before, so numerous tests don't know about it.
[[ "$arch" == "MINGW6" ]] && arch=CYGWIN
export AUTOBUILD="$(which autobuild)"
# Build with a tag like "Second_Life_Project_Shiny#abcdef0" to get a
# viewer channel "Second Life Project Shiny" (ignoring "#hash",
# needed to disambiguate tags).
if [[ "$GITHUB_REF_TYPE" == "tag" && "${GITHUB_REF_NAME:0:12}" == "Second_Life_" ]]
then viewer_channel="${GITHUB_REF_NAME%#*}"
export viewer_channel="${viewer_channel//_/ }"
else export viewer_channel="Second Life Test"
fi
echo "viewer_channel=$viewer_channel" >> "$GITHUB_OUTPUT"
# On windows we need to point the build to the correct python
# as neither CMake's FindPython nor our custom Python.cmake module
# will resolve the correct interpreter location.
if [[ "$RUNNER_OS" == "Windows" ]]; then
export PYTHON="$(cygpath -m "$(which python)")"
export PYTHON="$(native_path "$(which python)")"
echo "Python location: $PYTHON"
export PYTHON_COMMAND="$PYTHON"
else
export PYTHON_COMMAND="python3"
fi
autobuild configure -- -DVIEWER_CHANNEL="Second Life Test ${GIT_REF##*/}"
autobuild build --no-configure
export PYTHON_COMMAND_NATIVE="$(native_path "$PYTHON_COMMAND")"
# Find artifacts
if [[ "$RUNNER_OS" == "Windows" ]]; then
installer_path=$(find ./build-*/newview/ | grep '_Setup\.exe')
installer_name="$(basename $installer_path)"
elif [[ "$RUNNER_OS" == "macOS" ]]; then
installer_path=$(find ./build-*/newview/ | grep '\.dmg')
installer_name="$(basename $installer_path)"
./build.sh
# Each artifact is downloaded as a distinct .zip file. Multiple jobs
# (per the matrix above) writing the same filepath to the same
# artifact name will *overwrite* that file. Moreover, they can
# interfere with each other, causing the upload to fail.
# https://github.com/actions/upload-artifact#uploading-to-the-same-artifact
# Given the size of our installers, and the fact that we typically
# only want to download just one instead of a single zip containing
# several, generate a distinct artifact name for each installer.
# If the matrix above can run multiple builds on the same
# platform, we must disambiguate on more than the platform name.
# e.g. if we were still running Windows 32-bit builds, we'd need to
# qualify the artifact with bit width.
if [[ "$AUTOBUILD_CONFIGURATION" == "ReleaseOS" ]]
then cfg_suffix='OS'
else cfg_suffix=''
fi
echo "artifact=$RUNNER_OS$cfg_suffix" >> $GITHUB_OUTPUT
echo "installer_path=$installer_path" >> $GITHUB_OUTPUT
echo "installer_name=$installer_name" >> $GITHUB_OUTPUT
- name: Upload installer
- name: Upload executable
if: matrix.configuration != 'ReleaseOS' && steps.build.outputs.viewer_app
uses: actions/upload-artifact@v3
with:
name: ${{ steps.build.outputs.installer_name }}
path: ${{ steps.build.outputs.installer_path }}
name: "${{ steps.build.outputs.artifact }}-app"
path: |
${{ steps.build.outputs.viewer_app }}
# The other upload of nontrivial size is the symbol file. Use a distinct
# artifact for that too.
- name: Upload symbol file
if: matrix.configuration != 'ReleaseOS'
uses: actions/upload-artifact@v3
with:
name: "${{ steps.build.outputs.artifact }}-symbols"
path: |
${{ steps.build.outputs.symbolfile }}
- name: Upload metadata
if: matrix.configuration != 'ReleaseOS'
uses: actions/upload-artifact@v3
with:
name: "${{ steps.build.outputs.artifact }}-metadata"
# emitted by build.sh, possibly multiple lines
path: |
${{ steps.build.outputs.metadata }}
- name: Upload physics package
uses: actions/upload-artifact@v3
# should only be set for viewer-private
if: matrix.configuration != 'ReleaseOS' && steps.build.outputs.physicstpv
with:
name: "${{ steps.build.outputs.artifact }}-physics"
# emitted by build.sh, zero or one lines
path: |
${{ steps.build.outputs.physicstpv }}
sign-and-package-windows:
needs: build
runs-on: windows
steps:
- name: Sign and package Windows viewer
uses: secondlife/viewer-build-util/sign-pkg-windows@main
with:
vault_uri: "${{ secrets.AZURE_KEY_VAULT_URI }}"
cert_name: "${{ secrets.AZURE_CERT_NAME }}"
client_id: "${{ secrets.AZURE_CLIENT_ID }}"
client_secret: "${{ secrets.AZURE_CLIENT_SECRET }}"
tenant_id: "${{ secrets.AZURE_TENANT_ID }}"
sign-and-package-mac:
needs: build
runs-on: macos-latest
steps:
- name: Unpack Mac notarization credentials
id: note-creds
shell: bash
run: |
# In NOTARIZE_CREDS_MACOS we expect to find:
# USERNAME="..."
# PASSWORD="..."
# TEAM_ID="..."
eval "${{ secrets.NOTARIZE_CREDS_MACOS }}"
echo "::add-mask::$USERNAME"
echo "::add-mask::$PASSWORD"
echo "::add-mask::$TEAM_ID"
echo "note_user=$USERNAME" >> "$GITHUB_OUTPUT"
echo "note_pass=$PASSWORD" >> "$GITHUB_OUTPUT"
echo "note_team=$TEAM_ID" >> "$GITHUB_OUTPUT"
# If we didn't manage to retrieve all of these credentials, better
# find out sooner than later.
[[ -n "$USERNAME" && -n "$PASSWORD" && -n "$TEAM_ID" ]]
- name: Sign and package Mac viewer
uses: secondlife/viewer-build-util/sign-pkg-mac@main
with:
channel: ${{ needs.build.outputs.viewer_channel }}
imagename: ${{ needs.build.outputs.imagename }}
cert_base64: ${{ secrets.SIGNING_CERT_MACOS }}
cert_name: ${{ secrets.SIGNING_CERT_MACOS_IDENTITY }}
cert_pass: ${{ secrets.SIGNING_CERT_MACOS_PASSWORD }}
note_user: ${{ steps.note-creds.outputs.note_user }}
note_pass: ${{ steps.note-creds.outputs.note_pass }}
note_team: ${{ steps.note-creds.outputs.note_team }}
post-windows-symbols:
needs: build
runs-on: ubuntu-latest
steps:
- name: Post Windows symbols
uses: secondlife/viewer-build-util/post-bugsplat-windows@main
with:
username: ${{ secrets.BUGSPLAT_USER }}
password: ${{ secrets.BUGSPLAT_PASS }}
database: "SecondLife_Viewer_2018"
channel: ${{ needs.build.outputs.viewer_channel }}
version: ${{ needs.build.outputs.viewer_version }}
post-mac-symbols:
needs: build
runs-on: ubuntu-latest
steps:
- name: Post Mac symbols
uses: secondlife/viewer-build-util/post-bugsplat-mac@main
with:
username: ${{ secrets.BUGSPLAT_USER }}
password: ${{ secrets.BUGSPLAT_PASS }}
database: "SecondLife_Viewer_2018"
channel: ${{ needs.build.outputs.viewer_channel }}
version: ${{ needs.build.outputs.viewer_version }}
release:
needs: [sign-and-package-windows, sign-and-package-mac]
runs-on: ubuntu-latest
if: github.ref_type == 'tag' && startsWith(github.ref_name, 'Second_Life_')
steps:
- uses: actions/download-artifact@v3
with:
path: artifacts
- name: Reshuffle artifact files
uses: secondlife/viewer-build-util/release-artifacts@main
with:
input-path: artifacts
output-path: assets
# The *-app artifacts are for use only by the signing and
# packaging steps. Once we've generated signed installers, we no
# longer need them, and we CERTAINLY don't want to publish
# thousands of individual files as separate URLs.
exclude: |-
Windows-app
macOS-app
# Use just "Windows" or "macOS" prefix because these are the only
# artifacts in which we expect files from both platforms with
# colliding names (e.g. autobuild-package.xml). release-artifacts
# normally resolves collisions by prepending the artifact name, so
# when we anticipate collisions, it's good to keep the prefix
# short and sweet.
prefix: |-
Windows-metadata=Windows
macOS-metadata=macOS
# forked from softprops/action-gh-release
- uses: secondlife-3p/action-gh-release@v1
with:
# name the release page for the build number so we can find it
# easily (analogous to looking up a codeticket build page)
name: "v${{ github.run_id }}"
prerelease: true
generate_release_notes: true
# the only reason we generate a GH release is to post build products
fail_on_unmatched_files: true
files: "assets/*"

View File

@ -11,7 +11,7 @@ jobs:
pre-commit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: actions/setup-python@v4
with:
python-version: 3.x

4
.gitignore vendored
View File

@ -86,4 +86,6 @@ tarfile_tmp
trivial_change_force_build
web/config.*
web/locale.*
web/secondlife.com.*
web/secondlife.com.*
.env

File diff suppressed because it is too large Load Diff

173
build.sh
View File

@ -16,6 +16,8 @@
# * The special style in which python is invoked is intentional to permit
# use of a native python install on windows - which requires paths in DOS form
cleanup="true"
retry_cmd()
{
max_attempts="$1"; shift
@ -110,6 +112,34 @@ installer_CYGWIN()
fi
}
[[ -n "$GITHUB_OUTPUT" ]] || fatal "Need to export GITHUB_OUTPUT"
# The following is based on the Warning for GitHub multiline output strings:
# https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#multiline-strings
EOF=$(dd if=/dev/urandom bs=15 count=1 status=none | base64)
# Build up these arrays as we go
metadata=()
symbolfile=()
physicstpv=()
# and dump them to GITHUB_OUTPUT when done
cleanup="$cleanup ; \
arrayoutput metadata ; \
arrayoutput symbolfile ; \
arrayoutput physicstpv"
trap "$cleanup" EXIT
arrayoutput()
{
local outputname="$1"
# append "[*]" to the array name so array indirection works
local array="$1[*]"
local IFS='
'
echo "$outputname<<$EOF
${!array}
$EOF" >> "$GITHUB_OUTPUT"
}
pre_build()
{
local variant="$1"
@ -121,7 +151,7 @@ pre_build()
RELEASE_CRASH_REPORTING=ON
HAVOK=ON
SIGNING=()
if [ "$arch" == "Darwin" -a "$variant" == "Release" ]
if [[ "$arch" == "Darwin" && "$variant" == "Release" ]]
then SIGNING=("-DENABLE_SIGNING:BOOL=YES" \
"-DSIGNING_IDENTITY:STRING=Developer ID Application: Linden Research, Inc.")
fi
@ -145,15 +175,27 @@ pre_build()
VIEWER_SYMBOL_FILE="$(native_path "$abs_build_dir/newview/$variant/secondlife-symbols-$symplat-${AUTOBUILD_ADDRSIZE}.tar.bz2")"
fi
# don't spew credentials into build log
bugsplat_sh="$build_secrets_checkout/bugsplat/bugsplat.sh"
set +x
if [ -r "$bugsplat_sh" ]
then # show that we're doing this, just not the contents
echo source "$bugsplat_sh"
source "$bugsplat_sh"
# expect these variables to be set in the environment from GitHub secrets
if [[ -n "$BUGSPLAT_DB" ]]
then
# don't spew credentials into build log
set +x
if [[ -z "$BUGSPLAT_USER" || -z "$BUGSPLAT_PASS" ]]
then
# older mechanism involving build-secrets repo -
# if build_secrets_checkout isn't set, report its name
bugsplat_sh="${build_secrets_checkout:-\$build_secrets_checkout}/bugsplat/bugsplat.sh"
if [ -r "$bugsplat_sh" ]
then # show that we're doing this, just not the contents
echo source "$bugsplat_sh"
source "$bugsplat_sh"
else
fatal "BUGSPLAT_USER or BUGSPLAT_PASS missing, and no $bugsplat_sh"
fi
fi
set -x
export BUGSPLAT_USER BUGSPLAT_PASS
fi
set -x
# honor autobuild_configure_parameters same as sling-buildscripts
eval_autobuild_configure_parameters=$(eval $(echo echo $autobuild_configure_parameters))
@ -181,13 +223,17 @@ package_llphysicsextensions_tpv()
# nat 2016-12-21: without HAVOK, can't build PhysicsExtensions_TPV.
if [ "$variant" = "Release" -a "${HAVOK:-}" != "OFF" ]
then
test -r "$build_dir/packages/llphysicsextensions/autobuild-tpv.xml" || fatal "No llphysicsextensions_tpv autobuild configuration found"
tpvconfig=$(native_path "$build_dir/packages/llphysicsextensions/autobuild-tpv.xml")
"$autobuild" build --quiet --config-file "$tpvconfig" -c Tpv || fatal "failed to build llphysicsextensions_tpv"
tpvconfig="$build_dir/packages/llphysicsextensions/autobuild-tpv.xml"
test -r "$tpvconfig" || fatal "No llphysicsextensions_tpv autobuild configuration found"
# SL-19942: autobuild ignores -c switch if AUTOBUILD_CONFIGURATION set
unset AUTOBUILD_CONFIGURATION
"$autobuild" build --quiet --config-file "$(native_path "$tpvconfig")" -c Tpv \
|| fatal "failed to build llphysicsextensions_tpv"
# capture the package file name for use in upload later...
PKGTMP=`mktemp -t pgktpv.XXXXXX`
trap "rm $PKGTMP* 2>/dev/null" 0
cleanup="$cleanup ; rm $PKGTMP* 2>/dev/null"
trap "$cleanup" EXIT
"$autobuild" package --quiet --config-file "$tpvconfig" --results-file "$(native_path $PKGTMP)" || fatal "failed to package llphysicsextensions_tpv"
tpv_status=$?
if [ -r "${PKGTMP}" ]
@ -313,12 +359,20 @@ begin_section "coding policy check"
# this far. Running coding policy checks on one platform *should* suffice...
if [[ "$arch" == "Darwin" ]]
then
# install the git-hooks dependencies
pip install -r "$(native_path "$git_hooks_checkout/requirements.txt")" || \
fatal "pip install git-hooks failed"
# validate the branch we're about to build
python_cmd "$git_hooks_checkout/coding_policy_git.py" --all_files || \
fatal "coding policy check failed"
git_hooks_reqs="$git_hooks_checkout/requirements.txt"
if [[ -r "$(shell_path "$git_hooks_reqs")" ]]
then
# install the git-hooks dependencies
pip install -r "$(native_path "$git_hooks_reqs")" || \
fatal "pip install git-hooks failed"
fi
git_hooks_script="$git_hooks_checkout/coding_policy_git.py"
if [[ -r "$(shell_path "$git_hooks_script")" ]]
then
# validate the branch we're about to build
python_cmd "$(native_path "$git_hooks_script")" --all_files || \
fatal "coding policy check failed"
fi
fi
end_section "coding policy check"
@ -353,6 +407,7 @@ do
begin_section "Autobuild metadata"
python_cmd "$helpers/codeticket.py" addoutput "Autobuild Metadata" "$build_dir/autobuild-package.xml" --mimetype text/xml \
|| fatal "Upload of autobuild metadata failed"
metadata+=("$build_dir/autobuild-package.xml")
if [ "$arch" != "Linux" ]
then
record_dependencies_graph "$build_dir/autobuild-package.xml" # defined in buildscripts/hg/bin/build.sh
@ -366,8 +421,11 @@ do
if [ -r "$build_dir/newview/viewer_version.txt" ]
then
begin_section "Viewer Version"
python_cmd "$helpers/codeticket.py" addoutput "Viewer Version" "$(<"$build_dir/newview/viewer_version.txt")" --mimetype inline-text \
viewer_version="$(<"$build_dir/newview/viewer_version.txt")"
python_cmd "$helpers/codeticket.py" addoutput "Viewer Version" "$viewer_version" --mimetype inline-text \
|| fatal "Upload of viewer version failed"
metadata+=("$build_dir/newview/viewer_version.txt")
echo "viewer_version=$viewer_version" >> "$GITHUB_OUTPUT"
end_section "Viewer Version"
fi
;;
@ -376,12 +434,14 @@ do
then
record_event "Doxygen warnings generated; see doxygen_warnings.log"
python_cmd "$helpers/codeticket.py" addoutput "Doxygen Log" "$build_dir/doxygen_warnings.log" --mimetype text/plain ## TBD
metadata+=("$build_dir/doxygen_warnings.log")
fi
if [ -d "$build_dir/doxygen/html" ]
then
tar -c -f "$build_dir/viewer-doxygen.tar.bz2" --strip-components 3 "$build_dir/doxygen/html"
python_cmd "$helpers/codeticket.py" addoutput "Doxygen Tarball" "$build_dir/viewer-doxygen.tar.bz2" \
|| fatal "Upload of doxygen tarball failed"
metadata+=("$build_dir/viewer-doxygen.tar.bz2")
fi
;;
*)
@ -486,64 +546,29 @@ then
if $build_viewer
then
begin_section "Uploads"
# Upload installer
package=$(installer_$arch)
if [ x"$package" = x ] || test -d "$package"
# nat 2016-12-22: without RELEASE_CRASH_REPORTING, we have no symbol file.
if [ "${RELEASE_CRASH_REPORTING:-}" != "OFF" ]
then
fatal "No installer found from `pwd`"
succeeded=$build_coverity
else
# Upload base package.
retry_cmd 4 30 python_cmd "$helpers/codeticket.py" addoutput Installer "$package" \
|| fatal "Upload of installer failed"
wait_for_codeticket
# Upload additional packages.
for package_id in $additional_packages
do
package=$(installer_$arch "$package_id")
if [ x"$package" != x ]
# BugSplat wants to see xcarchive.zip
# e.g. build-darwin-x86_64/newview/Release/Second Life Test.xcarchive.zip
symbol_file="${build_dir}/newview/${variant}/${viewer_channel}.xcarchive.zip"
if [[ ! -f "$symbol_file" ]]
then
retry_cmd 4 30 python_cmd "$helpers/codeticket.py" addoutput "Installer $package_id" "$package" \
|| fatal "Upload of installer $package_id failed"
wait_for_codeticket
else
record_failure "Failed to find additional package for '$package_id'."
# symbol tarball we prep for (e.g.) Breakpad
symbol_file="$VIEWER_SYMBOL_FILE"
fi
done
# Upload crash reporter file
symbolfile+=("$symbol_file")
fi
if [ "$last_built_variant" = "Release" ]
then
# nat 2016-12-22: without RELEASE_CRASH_REPORTING, we have no symbol file.
if [ "${RELEASE_CRASH_REPORTING:-}" != "OFF" ]
then
# Upload crash reporter file
retry_cmd 4 30 python_cmd "$helpers/codeticket.py" addoutput "Symbolfile" "$VIEWER_SYMBOL_FILE" \
|| fatal "Upload of symbolfile failed"
wait_for_codeticket
fi
# Upload the llphysicsextensions_tpv package, if one was produced
# *TODO: Make this an upload-extension
if [ -r "$build_dir/llphysicsextensions_package" ]
then
llphysicsextensions_package=$(cat $build_dir/llphysicsextensions_package)
retry_cmd 4 30 python_cmd "$helpers/codeticket.py" addoutput "Physics Extensions Package" "$llphysicsextensions_package" --private \
|| fatal "Upload of physics extensions package failed"
fi
fi
# Run upload extensions
# Ex: bugsplat
if [ -d ${build_dir}/packages/upload-extensions ]; then
for extension in ${build_dir}/packages/upload-extensions/*.sh; do
begin_section "Upload Extension $extension"
. $extension
[ $? -eq 0 ] || fatal "Upload of extension $extension failed"
wait_for_codeticket
end_section "Upload Extension $extension"
done
fi
# Upload the llphysicsextensions_tpv package, if one was produced
# Only upload this package when building the private repo so the
# artifact is private.
if [[ "x$GITHUB_REPOSITORY" == "xsecondlife/viewer-private" && \
-r "$build_dir/llphysicsextensions_package" ]]
then
llphysicsextensions_package=$(cat $build_dir/llphysicsextensions_package)
physicstpv+=("$llphysicsextensions_package")
fi
end_section "Uploads"
else

View File

@ -0,0 +1,60 @@
# standalone functions from sling-buildscripts
set_build_number_to_revision()
{
record_event "buildNumber $revision"
}
record_event()
{
echo "=== $@"
}
begin_section()
{
record_event "START $*"
sections+=("$*")
}
end_section()
{
# accommodate dumb Mac bash 3, which doesn't understand array[-1]
local last=$(( ${#sections[@]} - 1 ))
record_event "END ${*:-${sections[$last]}}"
unset "sections[$last]"
}
record_success()
{
record_event "SUCCESS $*"
}
record_failure()
{
record_event "FAILURE $*" >&2
}
fatal()
{
record_failure "$@"
finalize false
exit 1
}
# redefined fail for backward compatibility
alias fail=fatal
pass()
{
exit 0
}
export -f set_build_number_to_revision
export -f record_event
export -f begin_section
export -f end_section
export -f record_success
export -f record_failure
export -f fatal
export -f pass
export sections

View File

@ -26,6 +26,11 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} $ENV{LL_BUILD}")
# Portable compilation flags.
add_compile_definitions( ADDRESS_SIZE=${ADDRESS_SIZE})
# Because older versions of Boost.Bind dumped placeholders _1, _2 et al. into
# the global namespace, Boost now requires either BOOST_BIND_NO_PLACEHOLDERS
# to avoid that or BOOST_BIND_GLOBAL_PLACEHOLDERS to state that we require it
# -- which we do. Without one or the other, we get a ton of Boost warnings.
add_compile_definitions(BOOST_BIND_GLOBAL_PLACEHOLDERS)
# Configure crash reporting
set(RELEASE_CRASH_REPORTING OFF CACHE BOOL "Enable use of crash reporting in release builds")
@ -55,15 +60,6 @@ if (WINDOWS)
# http://www.cmake.org/pipermail/cmake/2009-September/032143.html
string(REPLACE "/Zm1000" " " CMAKE_CXX_FLAGS ${CMAKE_CXX_FLAGS})
# Without PreferredToolArchitecture=x64, as of 2020-06-26 the 32-bit
# compiler on our TeamCity build hosts has started running out of virtual
# memory for the precompiled header file.
# CP changed to only append the flag for 32bit builds - on 64bit builds,
# locally at least, the build output is spammed with 1000s of 'D9002'
# warnings about this switch being ignored.
if(ADDRESS_SIZE EQUAL 32 AND DEFINED ENV{"TEAMCITY_PROJECT_NAME"})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /p:PreferredToolArchitecture=x64")
endif()
# zlib has assembly-language object files incompatible with SAFESEH
add_link_options(/LARGEADDRESSAWARE
/SAFESEH:NO

View File

@ -16,7 +16,6 @@ if (WINDOWS)
endif (LLCOMMON_LINK_SHARED)
target_link_libraries( ll::apr INTERFACE
${ARCH_PREBUILT_DIRS_RELEASE}/${APR_selector}apr-1.lib
${ARCH_PREBUILT_DIRS_RELEASE}/${APR_selector}apriconv-1.lib
${ARCH_PREBUILT_DIRS_RELEASE}/${APR_selector}aprutil-1.lib
)
elseif (DARWIN)
@ -37,7 +36,6 @@ else (WINDOWS)
target_link_libraries( ll::apr INTERFACE
apr-1
aprutil-1
iconv
uuid
rt
)

View File

@ -57,7 +57,6 @@ if(WINDOWS)
openjp2.dll
libapr-1.dll
libaprutil-1.dll
libapriconv-1.dll
nghttp2.dll
libhunspell.dll
uriparser.dll
@ -172,7 +171,6 @@ elseif(DARWIN)
libndofdev.dylib
libnghttp2.dylib
libnghttp2.14.dylib
libnghttp2.14.19.0.dylib
liburiparser.dylib
liburiparser.1.dylib
liburiparser.1.0.27.dylib

View File

@ -62,6 +62,7 @@ elseif (WINDOWS)
user32
ole32
dbghelp
rpcrt4.lib
legacy_stdio_definitions
)
else()

View File

@ -38,6 +38,7 @@ import itertools
import operator
import os
import re
import shlex
import shutil
import subprocess
import sys
@ -531,15 +532,15 @@ class LLManifest(object, metaclass=LLManifestRegistry):
self.cmakedirs(path)
return path
def run_command(self, command):
def run_command(self, command, **kwds):
"""
Runs an external command.
Raises ManifestError exception if the command returns a nonzero status.
"""
print("Running command:", command)
print("Running command:", shlex.join(command))
sys.stdout.flush()
try:
subprocess.check_call(command)
subprocess.check_call(command, **kwds)
except subprocess.CalledProcessError as err:
raise ManifestError( "Command %s returned non-zero status (%s)"
% (command, err.returncode) )

View File

@ -30,6 +30,7 @@
#include <list>
#include <map>
#include <array>
#include "v3math.h"
#include "v3dmath.h"

View File

@ -42,6 +42,7 @@ class LLAudioEngine_OpenAL : public LLAudioEngine
virtual bool init(void *user_data, const std::string &app_title);
virtual std::string getDriverName(bool verbose);
virtual LLStreamingAudioInterface* createDefaultStreamingAudioImpl() const { return nullptr; }
virtual void allocateListener();
virtual void shutdown();
@ -56,7 +57,6 @@ class LLAudioEngine_OpenAL : public LLAudioEngine
/*virtual*/ void updateWind(LLVector3 direction, F32 camera_altitude);
private:
void * windDSP(void *newbuffer, int length);
typedef S16 WIND_SAMPLE_T;
LLWindGen<WIND_SAMPLE_T> *mWindGen;
S16 *mWindBuf;

View File

@ -123,11 +123,7 @@ LLCoros::LLCoros():
// Previously we used
// boost::context::guarded_stack_allocator::default_stacksize();
// empirically this is insufficient.
#if ADDRESS_SIZE == 64
mStackSize(512*1024),
#else
mStackSize(256*1024),
#endif
mStackSize(768*1024),
// mCurrent does NOT own the current CoroData instance -- it simply
// points to it. So initialize it with a no-op deleter.
mCurrent{ [](CoroData*){} }

View File

@ -1603,5 +1603,18 @@ namespace LLError
}
}
void crashdriver(void (*callback)(int*))
{
// The LLERROR_CRASH macro used to have inline code of the form:
//int* make_me_crash = NULL;
//*make_me_crash = 0;
// But compilers are getting smart enough to recognize that, so we must
// assign to an address supplied by a separate source file. We could do
// the assignment here in crashdriver() -- but then BugSplat would group
// all LL_ERRS() crashes as the fault of this one function, instead of
// identifying the specific LL_ERRS() source line. So instead, do the
// assignment in a lambda in the caller's source. We just provide the
// nullptr target.
callback(nullptr);
}

View File

@ -385,11 +385,9 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
#define LL_NEWLINE '\n'
// Use this only in LL_ERRS or in a place that LL_ERRS may not be used
#define LLERROR_CRASH \
{ \
int* make_me_crash = NULL;\
*make_me_crash = 0; \
exit(*make_me_crash); \
#define LLERROR_CRASH \
{ \
crashdriver([](int* ptr){ *ptr = 0; exit(*ptr); }); \
}
#define LL_ENDL \
@ -491,4 +489,7 @@ LL_DEBUGS("SomeTag") performs the locking and map-searching ONCE, then caches
the result in a static variable.
*/
// used by LLERROR_CRASH
void crashdriver(void (*)(int*));
#endif // LL_LLERROR_H

View File

@ -389,6 +389,17 @@ public:
// Read all remaining bytes and log.
LL_INFOS("LLLeap") << mDesc << ": " << rest << LL_ENDL;
}
/*--------------------------- diagnostic ---------------------------*/
else if (data["eof"].asBoolean())
{
LL_DEBUGS("LLLeap") << mDesc << " ended, no partial line" << LL_ENDL;
}
else
{
LL_DEBUGS("LLLeap") << mDesc << " (still running, " << childerr.size()
<< " bytes pending)" << LL_ENDL;
}
/*------------------------- end diagnostic -------------------------*/
return false;
}

View File

@ -746,7 +746,7 @@ private:
__cpuid(0x1, eax, ebx, ecx, edx);
if(feature_infos[0] != (S32)edx)
{
LL_ERRS() << "machdep.cpu.feature_bits doesn't match expected cpuid result!" << LL_ENDL;
LL_WARNS() << "machdep.cpu.feature_bits doesn't match expected cpuid result!" << LL_ENDL;
}
#endif // LL_RELEASE_FOR_DOWNLOAD

View File

@ -58,46 +58,14 @@
* to restore uniform distribution.
*/
// *NOTE: The system rand implementation is probably not correct.
#define LL_USE_SYSTEM_RAND 0
#if LL_USE_SYSTEM_RAND
#include <cstdlib>
#endif
#if LL_USE_SYSTEM_RAND
class LLSeedRand
{
public:
LLSeedRand()
{
#if LL_WINDOWS
srand(LLUUID::getRandomSeed());
#else
srand48(LLUUID::getRandomSeed());
#endif
}
};
static LLSeedRand sRandomSeeder;
inline F64 ll_internal_random_double()
{
#if LL_WINDOWS
return (F64)rand() / (F64)RAND_MAX;
#else
return drand48();
#endif
}
inline F32 ll_internal_random_float()
{
#if LL_WINDOWS
return (F32)rand() / (F32)RAND_MAX;
#else
return (F32)drand48();
#endif
}
#else
static LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed());
inline F64 ll_internal_random_double()
// no default implementation, only specific F64 and F32 specializations
template <typename REAL>
inline REAL ll_internal_random();
template <>
inline F64 ll_internal_random<F64>()
{
// *HACK: Through experimentation, we have found that dual core
// CPUs (or at least multi-threaded processes) seem to
@ -108,15 +76,35 @@ inline F64 ll_internal_random_double()
return rv;
}
template <>
inline F32 ll_internal_random<F32>()
{
return F32(ll_internal_random<F64>());
}
/*------------------------------ F64 aliases -------------------------------*/
inline F64 ll_internal_random_double()
{
return ll_internal_random<F64>();
}
F64 ll_drand()
{
return ll_internal_random_double();
}
/*------------------------------ F32 aliases -------------------------------*/
inline F32 ll_internal_random_float()
{
// The clamping rules are described above.
F32 rv = (F32)gRandomGenerator();
if(!((rv >= 0.0f) && (rv < 1.0f))) return fmod(rv, 1.f);
return rv;
return ll_internal_random<F32>();
}
#endif
F32 ll_frand()
{
return ll_internal_random_float();
}
/*-------------------------- clamped random range --------------------------*/
S32 ll_rand()
{
return ll_rand(RAND_MAX);
@ -130,42 +118,28 @@ S32 ll_rand(S32 val)
return rv;
}
F32 ll_frand()
template <typename REAL>
REAL ll_grand(REAL val)
{
return ll_internal_random_float();
// The clamping rules are described above.
REAL rv = ll_internal_random<REAL>() * val;
if(val > 0)
{
if(rv >= val) return REAL();
}
else
{
if(rv <= val) return REAL();
}
return rv;
}
F32 ll_frand(F32 val)
{
// The clamping rules are described above.
F32 rv = ll_internal_random_float() * val;
if(val > 0)
{
if(rv >= val) return 0.0f;
}
else
{
if(rv <= val) return 0.0f;
}
return rv;
}
F64 ll_drand()
{
return ll_internal_random_double();
return ll_grand<F32>(val);
}
F64 ll_drand(F64 val)
{
// The clamping rules are described above.
F64 rv = ll_internal_random_double() * val;
if(val > 0)
{
if(rv >= val) return 0.0;
}
else
{
if(rv <= val) return 0.0;
}
return rv;
return ll_grand<F64>(val);
}

View File

@ -17,8 +17,6 @@
// std headers
#include <functional>
// external library headers
#include <boost/assign/list_of.hpp>
#include <boost/phoenix/core/argument.hpp>
// other Linden headers
#include "../test/lltut.h"
#include "../test/namedtempfile.h"
@ -30,10 +28,6 @@
#include "stringize.h"
#include "StringVec.h"
using boost::assign::list_of;
StringVec sv(const StringVec& listof) { return listof; }
#if defined(LL_WINDOWS)
#define sleep(secs) _sleep((secs) * 1000)
@ -104,17 +98,12 @@ namespace tut
llleap_data():
reader(".py",
// This logic is adapted from vita.viewerclient.receiveEvent()
boost::phoenix::placeholders::arg1 <<
[](std::ostream& out){ out <<
"import re\n"
"import os\n"
"import sys\n"
"\n"
"try:\n"
// new freestanding llsd package
" import llsd\n"
"except ImportError:\n"
// older llbase.llsd module
" from llbase import llsd\n"
"import llsd\n"
"\n"
"class ProtocolError(Exception):\n"
" def __init__(self, msg, data):\n"
@ -193,7 +182,7 @@ namespace tut
"def request(pump, data):\n"
" # we expect 'data' is a dict\n"
" data['reply'] = _reply\n"
" send(pump, data)\n"),
" send(pump, data)\n";}),
// Get the actual pathname of the NamedExtTempFile and trim off
// the ".py" extension. (We could cache reader.getName() in a
// separate member variable, but I happen to know getName() just
@ -218,14 +207,14 @@ namespace tut
void object::test<1>()
{
set_test_name("multiple LLLeap instances");
NamedTempFile script("py",
"import time\n"
"time.sleep(1)\n");
NamedExtTempFile script("py",
"import time\n"
"time.sleep(1)\n");
LLLeapVector instances;
instances.push_back(LLLeap::create(get_test_name(),
sv(list_of(PYTHON)(script.getName())))->getWeak());
StringVec{PYTHON, script.getName()})->getWeak());
instances.push_back(LLLeap::create(get_test_name(),
sv(list_of(PYTHON)(script.getName())))->getWeak());
StringVec{PYTHON, script.getName()})->getWeak());
// In this case we're simply establishing that two LLLeap instances
// can coexist without throwing exceptions or bombing in any other
// way. Wait for them to terminate.
@ -236,10 +225,10 @@ namespace tut
void object::test<2>()
{
set_test_name("stderr to log");
NamedTempFile script("py",
"import sys\n"
"sys.stderr.write('''Hello from Python!\n"
"note partial line''')\n");
NamedExtTempFile script("py",
"import sys\n"
"sys.stderr.write('''Hello from Python!\n"
"note partial line''')\n");
StringVec vcommand{ PYTHON, script.getName() };
CaptureLog log(LLError::LEVEL_INFO);
waitfor(LLLeap::create(get_test_name(), vcommand));
@ -251,11 +240,11 @@ namespace tut
void object::test<3>()
{
set_test_name("bad stdout protocol");
NamedTempFile script("py",
"print('Hello from Python!')\n");
NamedExtTempFile script("py",
"print('Hello from Python!')\n");
CaptureLog log(LLError::LEVEL_WARN);
waitfor(LLLeap::create(get_test_name(),
sv(list_of(PYTHON)(script.getName()))));
StringVec{PYTHON, script.getName()}));
ensure_contains("error log line",
log.messageWith("invalid protocol"), "Hello from Python!");
}
@ -264,13 +253,13 @@ namespace tut
void object::test<4>()
{
set_test_name("leftover stdout");
NamedTempFile script("py",
"import sys\n"
// note lack of newline
"sys.stdout.write('Hello from Python!')\n");
NamedExtTempFile script("py",
"import sys\n"
// note lack of newline
"sys.stdout.write('Hello from Python!')\n");
CaptureLog log(LLError::LEVEL_WARN);
waitfor(LLLeap::create(get_test_name(),
sv(list_of(PYTHON)(script.getName()))));
StringVec{PYTHON, script.getName()}));
ensure_contains("error log line",
log.messageWith("Discarding"), "Hello from Python!");
}
@ -279,12 +268,12 @@ namespace tut
void object::test<5>()
{
set_test_name("bad stdout len prefix");
NamedTempFile script("py",
"import sys\n"
"sys.stdout.write('5a2:something')\n");
NamedExtTempFile script("py",
"import sys\n"
"sys.stdout.write('5a2:something')\n");
CaptureLog log(LLError::LEVEL_WARN);
waitfor(LLLeap::create(get_test_name(),
sv(list_of(PYTHON)(script.getName()))));
StringVec{PYTHON, script.getName()}));
ensure_contains("error log line",
log.messageWith("invalid protocol"), "5a2:");
}
@ -386,17 +375,18 @@ namespace tut
set_test_name("round trip");
AckAPI api;
Result result;
NamedTempFile script("py",
boost::phoenix::placeholders::arg1 <<
"from " << reader_module << " import *\n"
// make a request on our little API
"request(pump='" << api.getName() << "', data={})\n"
// wait for its response
"resp = get()\n"
"result = '' if resp == dict(pump=replypump(), data='ack')\\\n"
" else 'bad: ' + str(resp)\n"
"send(pump='" << result.getName() << "', data=result)\n");
waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))));
NamedExtTempFile script("py",
[&](std::ostream& out){ out <<
"from " << reader_module << " import *\n"
// make a request on our little API
"request(pump='" << api.getName() << "', data={})\n"
// wait for its response
"resp = get()\n"
"result = '' if resp == dict(pump=replypump(), data='ack')\\\n"
" else 'bad: ' + str(resp)\n"
"send(pump='" << result.getName() << "', data=result)\n";});
waitfor(LLLeap::create(get_test_name(),
StringVec{PYTHON, script.getName()}));
result.ensure();
}
@ -424,38 +414,38 @@ namespace tut
// iterations etc. in OS pipes and the LLLeap/LLProcess implementation.
ReqIDAPI api;
Result result;
NamedTempFile script("py",
boost::phoenix::placeholders::arg1 <<
"import sys\n"
"from " << reader_module << " import *\n"
// Note that since reader imports llsd, this
// 'import *' gets us llsd too.
"sample = llsd.format_notation(dict(pump='" <<
api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n"
// The whole packet has length prefix too: "len:data"
"samplen = len(str(len(sample))) + 1 + len(sample)\n"
// guess how many messages it will take to
// accumulate BUFFERED_LENGTH
"count = int(" << BUFFERED_LENGTH << "/samplen)\n"
"print('Sending %s requests' % count, file=sys.stderr)\n"
"for i in range(count):\n"
" request('" << api.getName() << "', dict(reqid=i))\n"
// The assumption in this specific test that
// replies will arrive in the same order as
// requests is ONLY valid because the API we're
// invoking sends replies instantly. If the API
// had to wait for some external event before
// sending its reply, replies could arrive in
// arbitrary order, and we'd have to tick them
// off from a set.
"result = ''\n"
"for i in range(count):\n"
" resp = get()\n"
" if resp['data']['reqid'] != i:\n"
" result = 'expected reqid=%s in %s' % (i, resp)\n"
" break\n"
"send(pump='" << result.getName() << "', data=result)\n");
waitfor(LLLeap::create(get_test_name(), sv(list_of(PYTHON)(script.getName()))),
NamedExtTempFile script("py",
[&](std::ostream& out){ out <<
"import sys\n"
"from " << reader_module << " import *\n"
// Note that since reader imports llsd, this
// 'import *' gets us llsd too.
"sample = llsd.format_notation(dict(pump='" <<
api.getName() << "', data=dict(reqid=999999, reply=replypump())))\n"
// The whole packet has length prefix too: "len:data"
"samplen = len(str(len(sample))) + 1 + len(sample)\n"
// guess how many messages it will take to
// accumulate BUFFERED_LENGTH
"count = int(" << BUFFERED_LENGTH << "/samplen)\n"
"print('Sending %s requests' % count, file=sys.stderr)\n"
"for i in range(count):\n"
" request('" << api.getName() << "', dict(reqid=i))\n"
// The assumption in this specific test that
// replies will arrive in the same order as
// requests is ONLY valid because the API we're
// invoking sends replies instantly. If the API
// had to wait for some external event before
// sending its reply, replies could arrive in
// arbitrary order, and we'd have to tick them
// off from a set.
"result = ''\n"
"for i in range(count):\n"
" resp = get()\n"
" if resp['data']['reqid'] != i:\n"
" result = 'expected reqid=%s in %s' % (i, resp)\n"
" break\n"
"send(pump='" << result.getName() << "', data=result)\n";});
waitfor(LLLeap::create(get_test_name(), StringVec{PYTHON, script.getName()}),
300); // needs more realtime than most tests
result.ensure();
}
@ -467,65 +457,62 @@ namespace tut
{
ReqIDAPI api;
Result result;
NamedTempFile script("py",
boost::phoenix::placeholders::arg1 <<
"import sys\n"
"from " << reader_module << " import *\n"
// Generate a very large string value.
"desired = int(sys.argv[1])\n"
// 7 chars per item: 6 digits, 1 comma
"count = int((desired - 50)/7)\n"
"large = ''.join('%06d,' % i for i in range(count))\n"
// Pass 'large' as reqid because we know the API
// will echo reqid, and we want to receive it back.
"request('" << api.getName() << "', dict(reqid=large))\n"
"try:\n"
" resp = get()\n"
"except ParseError as e:\n"
" # try to find where e.data diverges from expectation\n"
// Normally we'd expect a 'pump' key in there,
// too, with value replypump(). But Python
// serializes keys in a different order than C++,
// so incoming data start with 'data'.
// Truthfully, though, if we get as far as 'pump'
// before we find a difference, something's very
// strange.
" expect = llsd.format_notation(dict(data=dict(reqid=large)))\n"
" chunk = 40\n"
" for offset in range(0, max(len(e.data), len(expect)), chunk):\n"
" if e.data[offset:offset+chunk] != \\\n"
" expect[offset:offset+chunk]:\n"
" print('Offset %06d: expect %r,\\n'\\\n"
" ' get %r' %\\\n"
" (offset,\n"
" expect[offset:offset+chunk],\n"
" e.data[offset:offset+chunk]),\n"
" file=sys.stderr)\n"
" break\n"
" else:\n"
" print('incoming data matches expect?!', file=sys.stderr)\n"
" send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n"
" sys.exit(1)\n"
"\n"
"echoed = resp['data']['reqid']\n"
"if echoed == large:\n"
" send('" << result.getName() << "', '')\n"
" sys.exit(0)\n"
// Here we know echoed did NOT match; try to find where
"for i in range(count):\n"
" start = 7*i\n"
" end = 7*(i+1)\n"
" if end > len(echoed)\\\n"
" or echoed[start:end] != large[start:end]:\n"
" send('" << result.getName() << "',\n"
" 'at offset %s, expected %r but got %r' %\n"
" (start, large[start:end], echoed[start:end]))\n"
"sys.exit(1)\n");
NamedExtTempFile script("py",
[&](std::ostream& out){ out <<
"import sys\n"
"from " << reader_module << " import *\n"
// Generate a very large string value.
"desired = int(sys.argv[1])\n"
// 7 chars per item: 6 digits, 1 comma
"count = int((desired - 50)/7)\n"
"large = ''.join('%06d,' % i for i in range(count))\n"
// Pass 'large' as reqid because we know the API
// will echo reqid, and we want to receive it back.
"request('" << api.getName() << "', dict(reqid=large))\n"
"try:\n"
" resp = get()\n"
"except ParseError as e:\n"
" # try to find where e.data diverges from expectation\n"
// Normally we'd expect a 'pump' key in there,
// too, with value replypump(). But Python
// serializes keys in a different order than C++,
// so incoming data start with 'data'.
// Truthfully, though, if we get as far as 'pump'
// before we find a difference, something's very
// strange.
" expect = llsd.format_notation(dict(data=dict(reqid=large)))\n"
" chunk = 40\n"
" for offset in range(0, max(len(e.data), len(expect)), chunk):\n"
" if e.data[offset:offset+chunk] != \\\n"
" expect[offset:offset+chunk]:\n"
" print('Offset %06d: expect %r,\\n'\\\n"
" ' get %r' %\\\n"
" (offset,\n"
" expect[offset:offset+chunk],\n"
" e.data[offset:offset+chunk]),\n"
" file=sys.stderr)\n"
" break\n"
" else:\n"
" print('incoming data matches expect?!', file=sys.stderr)\n"
" send('" << result.getName() << "', '%s: %s' % (e.__class__.__name__, e))\n"
" sys.exit(1)\n"
"\n"
"echoed = resp['data']['reqid']\n"
"if echoed == large:\n"
" send('" << result.getName() << "', '')\n"
" sys.exit(0)\n"
// Here we know echoed did NOT match; try to find where
"for i in range(count):\n"
" start = 7*i\n"
" end = 7*(i+1)\n"
" if end > len(echoed)\\\n"
" or echoed[start:end] != large[start:end]:\n"
" send('" << result.getName() << "',\n"
" 'at offset %s, expected %r but got %r' %\n"
" (start, large[start:end], echoed[start:end]))\n"
"sys.exit(1)\n";});
waitfor(LLLeap::create(test_name,
sv(list_of
(PYTHON)
(script.getName())
(stringize(size)))),
StringVec{PYTHON, script.getName(), stringize(size)}),
180); // try a longer timeout
result.ensure();
}

View File

@ -151,8 +151,38 @@ struct PythonProcessLauncher
/// Launch Python script; verify that it launched
void launch()
{
mPy = LLProcess::create(mParams);
tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy));
try
{
mPy = LLProcess::create(mParams);
tut::ensure(STRINGIZE("Couldn't launch " << mDesc << " script"), bool(mPy));
}
catch (const tut::failure&)
{
// On Windows, if APR_LOG is set, our version of APR's
// apr_create_proc() logs to the specified file. If this test
// failed, try to report that log.
const char* APR_LOG = getenv("APR_LOG");
if (APR_LOG && *APR_LOG)
{
std::ifstream inf(APR_LOG);
if (! inf.is_open())
{
LL_WARNS() << "Couldn't open '" << APR_LOG << "'" << LL_ENDL;
}
else
{
LL_WARNS() << "==============================" << LL_ENDL;
LL_WARNS() << "From '" << APR_LOG << "':" << LL_ENDL;
std::string line;
while (std::getline(inf, line))
{
LL_WARNS() << line << LL_ENDL;
}
LL_WARNS() << "==============================" << LL_ENDL;
}
}
throw;
}
}
/// Run Python script and wait for it to complete.
@ -191,7 +221,7 @@ struct PythonProcessLauncher
LLProcess::Params mParams;
LLProcessPtr mPy;
std::string mDesc;
NamedTempFile mScript;
NamedExtTempFile mScript;
};
/// convenience function for PythonProcessLauncher::run()
@ -214,30 +244,26 @@ static std::string python_out(const std::string& desc, const CONTENT& script)
class NamedTempDir: public boost::noncopyable
{
public:
// Use python() function to create a temp directory: I've found
// nothing in either Boost.Filesystem or APR quite like Python's
// tempfile.mkdtemp().
// Special extra bonus: on Mac, mkdtemp() reports a pathname
// starting with /var/folders/something, whereas that's really a
// symlink to /private/var/folders/something. Have to use
// realpath() to compare properly.
NamedTempDir():
mPath(python_out("mkdtemp()",
"from __future__ import with_statement\n"
"import os.path, sys, tempfile\n"
"with open(sys.argv[1], 'w') as f:\n"
" f.write(os.path.normcase(os.path.normpath(os.path.realpath(tempfile.mkdtemp()))))\n"))
{}
mPath(NamedTempFile::temp_path()),
mCreated(boost::filesystem::create_directories(mPath))
{
mPath = boost::filesystem::canonical(mPath);
}
~NamedTempDir()
{
aprchk(apr_dir_remove(mPath.c_str(), gAPRPoolp));
if (mCreated)
{
boost::filesystem::remove_all(mPath);
}
}
std::string getName() const { return mPath; }
std::string getName() const { return mPath.string(); }
private:
std::string mPath;
boost::filesystem::path mPath;
bool mCreated;
};
/*****************************************************************************
@ -355,7 +381,7 @@ namespace tut
set_test_name("raw APR nonblocking I/O");
// Create a script file in a temporary place.
NamedTempFile script("py",
NamedExtTempFile script("py",
"from __future__ import print_function" EOL
"import sys" EOL
"import time" EOL
@ -565,7 +591,13 @@ namespace tut
" f.write(os.path.normcase(os.path.normpath(os.getcwd())))\n");
// Before running, call setWorkingDirectory()
py.mParams.cwd = tempdir.getName();
ensure_equals("os.getcwd()", py.run_read(), tempdir.getName());
std::string expected{ tempdir.getName() };
#if LL_WINDOWS
// SIGH, don't get tripped up by "C:" != "c:" --
// but on the Mac, using tolower() fails because "/users" != "/Users"!
expected = utf8str_tolower(expected);
#endif
ensure_equals("os.getcwd()", py.run_read(), expected);
}
template<> template<>

View File

@ -29,7 +29,23 @@
#include "../test/lltut.h"
#include "../llrand.h"
#include "stringize.h"
// In llrand.h, every function is documented to return less than the high end
// -- specifically, because you can pass a negative extent, they're documented
// never to return a value equal to the extent.
// So that we don't need two different versions of ensure_in_range(), when
// testing extent < 0, negate the return value and the extent before passing
// into ensure_in_range().
template <typename NUMBER>
void ensure_in_range(const std::string_view& name,
NUMBER value, NUMBER low, NUMBER high)
{
auto failmsg{ stringize(name, " >= ", low, " (", value, ')') };
tut::ensure(failmsg, (value >= low));
failmsg = stringize(name, " < ", high, " (", value, ')');
tut::ensure(failmsg, (value < high));
}
namespace tut
{
@ -44,84 +60,65 @@ namespace tut
template<> template<>
void random_object_t::test<1>()
{
F32 number = 0.0f;
for(S32 ii = 0; ii < 100000; ++ii)
{
number = ll_frand();
ensure("frand >= 0", (number >= 0.0f));
ensure("frand < 1", (number < 1.0f));
ensure_in_range("frand", ll_frand(), 0.0f, 1.0f);
}
}
template<> template<>
void random_object_t::test<2>()
{
F64 number = 0.0f;
for(S32 ii = 0; ii < 100000; ++ii)
{
number = ll_drand();
ensure("drand >= 0", (number >= 0.0));
ensure("drand < 1", (number < 1.0));
ensure_in_range("drand", ll_drand(), 0.0, 1.0);
}
}
template<> template<>
void random_object_t::test<3>()
{
F32 number = 0.0f;
for(S32 ii = 0; ii < 100000; ++ii)
{
number = ll_frand(2.0f) - 1.0f;
ensure("frand >= 0", (number >= -1.0f));
ensure("frand < 1", (number <= 1.0f));
ensure_in_range("frand(2.0f)", ll_frand(2.0f) - 1.0f, -1.0f, 1.0f);
}
}
template<> template<>
void random_object_t::test<4>()
{
F32 number = 0.0f;
for(S32 ii = 0; ii < 100000; ++ii)
{
number = ll_frand(-7.0);
ensure("drand <= 0", (number <= 0.0));
ensure("drand > -7", (number > -7.0));
// Negate the result so we don't have to allow a templated low-end
// comparison as well.
ensure_in_range("-frand(-7.0)", -ll_frand(-7.0), 0.0f, 7.0f);
}
}
template<> template<>
void random_object_t::test<5>()
{
F64 number = 0.0f;
for(S32 ii = 0; ii < 100000; ++ii)
{
number = ll_drand(-2.0);
ensure("drand <= 0", (number <= 0.0));
ensure("drand > -2", (number > -2.0));
ensure_in_range("-drand(-2.0)", -ll_drand(-2.0), 0.0, 2.0);
}
}
template<> template<>
void random_object_t::test<6>()
{
S32 number = 0;
for(S32 ii = 0; ii < 100000; ++ii)
{
number = ll_rand(100);
ensure("rand >= 0", (number >= 0));
ensure("rand < 100", (number < 100));
ensure_in_range("rand(100)", ll_rand(100), 0, 100);
}
}
template<> template<>
void random_object_t::test<7>()
{
S32 number = 0;
for(S32 ii = 0; ii < 100000; ++ii)
{
number = ll_rand(-127);
ensure("rand <= 0", (number <= 0));
ensure("rand > -127", (number > -127));
ensure_in_range("-rand(-127)", -ll_rand(-127), 0, 127);
}
}
}

View File

@ -45,11 +45,6 @@ typedef U32 uint32_t;
#endif
#include "boost/range.hpp"
#include "boost/foreach.hpp"
#include "boost/bind.hpp"
#include "boost/phoenix/bind/bind_function.hpp"
#include "boost/phoenix/core/argument.hpp"
using namespace boost::phoenix;
#include "llsd.h"
#include "llsdserialize.h"
@ -57,9 +52,11 @@ using namespace boost::phoenix;
#include "llformat.h"
#include "llmemorystream.h"
#include "../test/hexdump.h"
#include "../test/lltut.h"
#include "../test/namedtempfile.h"
#include "stringize.h"
#include "StringVec.h"
#include <functional>
typedef std::function<void(const LLSD& data, std::ostream& str)> FormatterFunction;
@ -1796,16 +1793,12 @@ namespace tut
// helper for TestPythonCompatible
static std::string import_llsd("import os.path\n"
"import sys\n"
"try:\n"
// new freestanding llsd package
" import llsd\n"
"except ImportError:\n"
// older llbase.llsd module
" from llbase import llsd\n");
"import llsd\n");
// helper for TestPythonCompatible
template <typename CONTENT>
void python(const std::string& desc, const CONTENT& script, int expect=0)
template <typename CONTENT, typename... ARGS>
void python_expect(const std::string& desc, const CONTENT& script, int expect=0,
ARGS&&... args)
{
auto PYTHON(LLStringUtil::getenv("PYTHON"));
ensure("Set $PYTHON to the Python interpreter", !PYTHON.empty());
@ -1816,7 +1809,8 @@ namespace tut
std::string q("\"");
std::string qPYTHON(q + PYTHON + q);
std::string qscript(q + scriptfile.getName() + q);
int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(), NULL);
int rc = _spawnl(_P_WAIT, PYTHON.c_str(), qPYTHON.c_str(), qscript.c_str(),
std::forward<ARGS>(args)..., NULL);
if (rc == -1)
{
char buffer[256];
@ -1832,6 +1826,10 @@ namespace tut
LLProcess::Params params;
params.executable = PYTHON;
params.args.add(scriptfile.getName());
for (const std::string& arg : StringVec{ std::forward<ARGS>(args)... })
{
params.args.add(arg);
}
LLProcessPtr py(LLProcess::create(params));
ensure(STRINGIZE("Couldn't launch " << desc << " script"), bool(py));
// Implementing timeout would mean messing with alarm() and
@ -1866,6 +1864,14 @@ namespace tut
#endif
}
// helper for TestPythonCompatible
template <typename CONTENT, typename... ARGS>
void python(const std::string& desc, const CONTENT& script, ARGS&&... args)
{
// plain python() expects rc 0
python_expect(desc, script, 0, std::forward<ARGS>(args)...);
}
struct TestPythonCompatible
{
TestPythonCompatible() {}
@ -1880,10 +1886,10 @@ namespace tut
void TestPythonCompatibleObject::test<1>()
{
set_test_name("verify python()");
python("hello",
"import sys\n"
"sys.exit(17)\n",
17); // expect nonzero rc
python_expect("hello",
"import sys\n"
"sys.exit(17)\n",
17); // expect nonzero rc
}
template<> template<>
@ -1899,7 +1905,7 @@ namespace tut
static void writeLLSDArray(const FormatterFunction& serialize,
std::ostream& out, const LLSD& array)
{
for (const LLSD& item : llsd::inArray(array))
for (const LLSD& item: llsd::inArray(array))
{
// It's important to delimit the entries in this file somehow
// because, although Python's llsd.parse() can accept a file
@ -1914,7 +1920,14 @@ namespace tut
auto buffstr{ buffer.str() };
int bufflen{ static_cast<int>(buffstr.length()) };
out.write(reinterpret_cast<const char*>(&bufflen), sizeof(bufflen));
LL_DEBUGS() << "Wrote length: "
<< hexdump(reinterpret_cast<const char*>(&bufflen),
sizeof(bufflen))
<< LL_ENDL;
out.write(buffstr.c_str(), buffstr.length());
LL_DEBUGS() << "Wrote data: "
<< hexmix(buffstr.c_str(), buffstr.length())
<< LL_ENDL;
}
}
@ -1943,10 +1956,10 @@ namespace tut
" else:\n"
" raise AssertionError('Too many data items')\n";
// Create an llsdXXXXXX file containing 'data' serialized to
// notation.
// Create an llsdXXXXXX file containing 'data' serialized per
// FormatterFunction.
NamedTempFile file("llsd",
// NamedTempFile's boost::function constructor
// NamedTempFile's function constructor
// takes a callable. To this callable it passes the
// std::ostream with which it's writing the
// NamedTempFile.
@ -1954,34 +1967,50 @@ namespace tut
(std::ostream& out)
{ writeLLSDArray(serialize, out, cdata); });
python("read C++ " + desc,
placeholders::arg1 <<
import_llsd <<
"from functools import partial\n"
"import io\n"
"import struct\n"
"lenformat = struct.Struct('i')\n"
"def parse_each(inf):\n"
" for rawlen in iter(partial(inf.read, lenformat.size), b''):\n"
" len = lenformat.unpack(rawlen)[0]\n"
// Since llsd.parse() has no max_bytes argument, instead of
// passing the input stream directly to parse(), read the item
// into a distinct bytes object and parse that.
" data = inf.read(len)\n"
" try:\n"
" frombytes = llsd.parse(data)\n"
" except llsd.LLSDParseError as err:\n"
" print(f'*** {err}')\n"
" print(f'Bad content:\\n{data!r}')\n"
" raise\n"
// Also try parsing from a distinct stream.
" stream = io.BytesIO(data)\n"
" fromstream = llsd.parse(stream)\n"
" assert frombytes == fromstream\n"
" yield frombytes\n"
<< pydata <<
// Don't forget raw-string syntax for Windows pathnames.
"verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n");
// 'debug' starts empty because it's intended as an output file
NamedTempFile debug("debug", "");
try
{
python("read C++ " + desc,
[&](std::ostream& out){ out <<
import_llsd <<
"from functools import partial\n"
"import io\n"
"import struct\n"
"lenformat = struct.Struct('i')\n"
"def parse_each(inf):\n"
" for rawlen in iter(partial(inf.read, lenformat.size), b''):\n"
" print('Read length:', ''.join(('%02x' % b) for b in rawlen),\n"
" file=debug)\n"
" len = lenformat.unpack(rawlen)[0]\n"
// Since llsd.parse() has no max_bytes argument, instead of
// passing the input stream directly to parse(), read the item
// into a distinct bytes object and parse that.
" data = inf.read(len)\n"
" print('Read data: ', repr(data), file=debug)\n"
" try:\n"
" frombytes = llsd.parse(data)\n"
" except llsd.LLSDParseError as err:\n"
" print(f'*** {err}')\n"
" print(f'Bad content:\\n{data!r}')\n"
" raise\n"
// Also try parsing from a distinct stream.
" stream = io.BytesIO(data)\n"
" fromstream = llsd.parse(stream)\n"
" assert frombytes == fromstream\n"
" yield frombytes\n"
<< pydata <<
// Don't forget raw-string syntax for Windows pathnames.
"debug = open(r'" << debug.getName() << "', 'w')\n"
"verify(parse_each(open(r'" << file.getName() << "', 'rb')))\n";});
}
catch (const failure&)
{
LL_DEBUGS() << "Script debug output:" << LL_ENDL;
debug.peep_log();
throw;
}
}
template<> template<>
@ -2068,7 +2097,7 @@ namespace tut
NamedTempFile file("llsd", "");
python("Python " + pyformatter,
placeholders::arg1 <<
[&](std::ostream& out){ out <<
import_llsd <<
"import struct\n"
"lenformat = struct.Struct('i')\n"
@ -2086,7 +2115,7 @@ namespace tut
" for item in DATA:\n"
" serialized = llsd." << pyformatter << "(item)\n"
" f.write(lenformat.pack(len(serialized)))\n"
" f.write(serialized)\n");
" f.write(serialized)\n";});
std::ifstream inf(file.getName().c_str());
LLSD item;

View File

@ -83,7 +83,11 @@ namespace tut
// signal the work item that it can quit; consider LLOneShotCond.
LLCond<Shared> data;
auto start = WorkSchedule::TimePoint::clock::now();
auto interval = 100ms;
// 2s seems like a long time to wait, since it directly impacts the
// duration of this test program. Unfortunately GitHub's Mac runners
// are pretty wimpy, and we're getting spurious "too late" errors just
// because the thread doesn't wake up as soon as we want.
auto interval = 2s;
queue.postEvery(
interval,
[&data, count = 0]

View File

@ -31,6 +31,7 @@
#include <algorithm>
#include "llsdserialize.h"
#include <boost/bind.hpp>
//=========================================================================
namespace

View File

@ -31,6 +31,7 @@
#include "lltrace.h"
#include "llfasttimer.h"
#include "v3colorutil.h"
#include <boost/bind.hpp>
//=========================================================================

View File

@ -32,6 +32,7 @@
#include "llfasttimer.h"
#include "v3colorutil.h"
#include "indra_constants.h"
#include <boost/bind.hpp>
const std::string LLSettingsWater::SETTING_BLUR_MULTIPLIER("blur_multiplier");
const std::string LLSettingsWater::SETTING_FOG_COLOR("water_fog_color");

View File

@ -33,7 +33,6 @@ import os
import sys
from http.server import HTTPServer, BaseHTTPRequestHandler
from llsd.fastest_elementtree import parse as xml_parse
import llsd
from testrunner import freeport, run, debug, VERBOSE
import time

View File

@ -2166,20 +2166,6 @@ if (PACKAGE AND (RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) AND VIE
)
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
@ -2197,24 +2183,22 @@ if (PACKAGE AND (RELEASE_CRASH_REPORTING OR NON_RELEASE_CRASH_REPORTING) AND VIE
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}"
DEPENDS "${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)
add_dependencies(generate_symbols dsym_xcarchive)
endif (DARWIN)
if (LINUX)
# TBD
endif (LINUX)
endif (USE_BUGSPLAT)
# for both Bugsplat and Breakpad
add_dependencies(llpackage generate_symbols)
add_dependencies(llpackage generate_symbols)
endif (USE_BUGSPLAT)
endif ()
if (LL_TESTS)

View File

@ -1 +1 @@
7.0.0
7.0.1

View File

@ -26,7 +26,6 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Compiler flags
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
Unicode true
SetOverwrite on # Overwrite files
SetCompress auto # Compress if saves space
SetCompressor /solid lzma # Compress whole installer as one block

View File

@ -3226,8 +3226,10 @@ LLSD LLAppViewer::getViewerInfo() const
// LLFloaterAbout.
LLSD info;
auto& versionInfo(LLVersionInfo::instance());
// With GitHub builds, the build number is too big to fit in a 32-bit int,
// and LLSD doesn't deal with integers wider than int. Use string.
info["VIEWER_VERSION"] = llsd::array(versionInfo.getMajor(), versionInfo.getMinor(),
versionInfo.getPatch(), versionInfo.getBuild());
versionInfo.getPatch(), stringize(versionInfo.getBuild()));
info["VIEWER_VERSION_STR"] = versionInfo.getVersion();
info["CHANNEL"] = versionInfo.getChannel();
info["ADDRESS_SIZE"] = ADDRESS_SIZE;
@ -3574,7 +3576,7 @@ void LLAppViewer::writeSystemInfo()
gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::instance().getMajor();
gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::instance().getMinor();
gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::instance().getPatch();
gDebugInfo["ClientInfo"]["BuildVersion"] = LLVersionInfo::instance().getBuild();
gDebugInfo["ClientInfo"]["BuildVersion"] = std::to_string(LLVersionInfo::instance().getBuild());
gDebugInfo["ClientInfo"]["AddressSize"] = LLVersionInfo::instance().getAddressSize();
gDebugInfo["CAFilename"] = gDirUtilp->getCAFile();
@ -5533,7 +5535,7 @@ void LLAppViewer::handleLoginComplete()
gDebugInfo["ClientInfo"]["MajorVersion"] = LLVersionInfo::instance().getMajor();
gDebugInfo["ClientInfo"]["MinorVersion"] = LLVersionInfo::instance().getMinor();
gDebugInfo["ClientInfo"]["PatchVersion"] = LLVersionInfo::instance().getPatch();
gDebugInfo["ClientInfo"]["BuildVersion"] = LLVersionInfo::instance().getBuild();
gDebugInfo["ClientInfo"]["BuildVersion"] = std::to_string(LLVersionInfo::instance().getBuild());
LLParcel* parcel = LLViewerParcelMgr::getInstance()->getAgentParcel();
if ( parcel && parcel->getMusicURL()[0])

View File

@ -45,6 +45,7 @@
#include "llxmlrpctransaction.h"
#include "llviewernetwork.h"
#include "llpanel.h"
#include "stringize.h"
const F64 CURRENCY_ESTIMATE_FREQUENCY = 2.0;
@ -158,7 +159,7 @@ void LLCurrencyUIManager::Impl::updateCurrencyInfo()
mLocalCurrencyEstimated = true;
return;
}
LLXMLRPCValue keywordArgs = LLXMLRPCValue::createStruct();
keywordArgs.appendString("agentId", gAgent.getID().asString());
keywordArgs.appendString(
@ -170,8 +171,10 @@ void LLCurrencyUIManager::Impl::updateCurrencyInfo()
keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::instance().getMajor());
keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::instance().getMinor());
keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::instance().getPatch());
keywordArgs.appendInt("viewerBuildVersion", LLVersionInfo::instance().getBuild());
// With GitHub builds, the build number is too big to fit in a 32-bit int,
// and XMLRPC_VALUE doesn't deal with integers wider than int. Use string.
keywordArgs.appendString("viewerBuildVersion", stringize(LLVersionInfo::instance().getBuild()));
LLXMLRPCValue params = LLXMLRPCValue::createArray();
params.append(keywordArgs);
@ -245,7 +248,9 @@ void LLCurrencyUIManager::Impl::startCurrencyBuy(const std::string& password)
keywordArgs.appendInt("viewerMajorVersion", LLVersionInfo::instance().getMajor());
keywordArgs.appendInt("viewerMinorVersion", LLVersionInfo::instance().getMinor());
keywordArgs.appendInt("viewerPatchVersion", LLVersionInfo::instance().getPatch());
keywordArgs.appendInt("viewerBuildVersion", LLVersionInfo::instance().getBuild());
// With GitHub builds, the build number is too big to fit in a 32-bit int,
// and XMLRPC_VALUE doesn't deal with integers wider than int. Use string.
keywordArgs.appendString("viewerBuildVersion", stringize(LLVersionInfo::instance().getBuild()));
LLXMLRPCValue params = LLXMLRPCValue::createArray();
params.append(keywordArgs);

View File

@ -42,6 +42,8 @@
#include <boost/signals2.hpp>
#include <array>
//-------------------------------------------------------------------------
class LLViewerCamera;
class LLParcel;

View File

@ -72,6 +72,8 @@
#include "llcorehttputil.h"
#include "lluiusage.h"
#include <array>
const static std::string ADHOC_NAME_SUFFIX(" Conference");
const static std::string NEARBY_P2P_BY_OTHER("nearby_P2P_by_other");

View File

@ -2409,6 +2409,8 @@ void LLInventoryGallery::startDrag()
ids.push_back(selected_id);
}
}
// We must have set this for some reason, but it's causing compile errors
(void)src;
LLToolDragAndDrop::getInstance()->beginMultiDrag(types, ids, LLToolDragAndDrop::SOURCE_AGENT);
}

View File

@ -41,7 +41,7 @@
#include <boost/algorithm/string/trim.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/regex/v4/match_results.hpp>
#include <boost/regex.hpp>
#include <boost/foreach.hpp>
#if LL_MSVC

View File

@ -65,6 +65,7 @@
#include "lltrans.h"
#include "llglheaders.h"
#include "llpanelloginlistener.h"
#include "stringize.h"
#if LL_WINDOWS
#pragma warning(disable: 4355) // 'this' used in initializer list
@ -300,10 +301,9 @@ LLPanelLogin::LLPanelLogin(const LLRect &rect,
setDefaultBtn(def_btn);
std::string channel = LLVersionInfo::instance().getChannel();
std::string version = llformat("%s (%d)",
LLVersionInfo::instance().getShortVersion().c_str(),
LLVersionInfo::instance().getBuild());
std::string version = stringize(LLVersionInfo::instance().getShortVersion(), " (",
LLVersionInfo::instance().getBuild(), ')');
LLTextBox* forgot_password_text = getChild<LLTextBox>("forgot_password_text");
forgot_password_text->setClickedCallback(onClickForgotPassword, NULL);
@ -894,9 +894,8 @@ void LLPanelLogin::loadLoginPage()
}
// Channel and Version
params["version"] = llformat("%s (%d)",
LLVersionInfo::instance().getShortVersion().c_str(),
LLVersionInfo::instance().getBuild());
params["version"] = stringize(LLVersionInfo::instance().getShortVersion(), " (",
LLVersionInfo::instance().getBuild(), ')');
params["channel"] = LLVersionInfo::instance().getChannel();
// Grid

View File

@ -39,6 +39,7 @@
#include "json/reader.h"
#include "llcorehttputil.h"
#include "llurlregistry.h"
#include "stringize.h"
static const std::string AZURE_NOTRANSLATE_OPENING_TAG("<div translate=\"no\">");
@ -160,12 +161,12 @@ void LLTranslationAPIHandler::verifyKeyCoro(LLTranslate::EService service, LLSD
LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders);
std::string user_agent = llformat("%s %d.%d.%d (%d)",
LLVersionInfo::instance().getChannel().c_str(),
LLVersionInfo::instance().getMajor(),
LLVersionInfo::instance().getMinor(),
LLVersionInfo::instance().getPatch(),
LLVersionInfo::instance().getBuild());
std::string user_agent = stringize(
LLVersionInfo::instance().getChannel(), ' ',
LLVersionInfo::instance().getMajor(), '.',
LLVersionInfo::instance().getMinor(), '.',
LLVersionInfo::instance().getPatch(), " (",
LLVersionInfo::instance().getBuild(), ')');
initHttpHeader(httpHeaders, user_agent, key);
@ -215,12 +216,12 @@ void LLTranslationAPIHandler::translateMessageCoro(LanguagePair_t fromTo, std::s
LLCore::HttpHeaders::ptr_t httpHeaders(new LLCore::HttpHeaders);
std::string user_agent = llformat("%s %d.%d.%d (%d)",
LLVersionInfo::instance().getChannel().c_str(),
LLVersionInfo::instance().getMajor(),
LLVersionInfo::instance().getMinor(),
LLVersionInfo::instance().getPatch(),
LLVersionInfo::instance().getBuild());
std::string user_agent = stringize(
LLVersionInfo::instance().getChannel(), ' ',
LLVersionInfo::instance().getMajor(), '.',
LLVersionInfo::instance().getMinor(), '.',
LLVersionInfo::instance().getPatch(), " (",
LLVersionInfo::instance().getBuild(), ')');
initHttpHeader(httpHeaders, user_agent);
httpOpts->setSSLVerifyPeer(false);

View File

@ -69,7 +69,7 @@ void LLVersionInfo::initSingleton()
// fully constructed; such calls don't really belong in the constructor.
// cache the version string
version = STRINGIZE(getShortVersion() << "." << getBuild());
version = stringize(getShortVersion(), ".", getBuild());
}
LLVersionInfo::~LLVersionInfo()
@ -91,7 +91,7 @@ S32 LLVersionInfo::getPatch()
return LL_VIEWER_VERSION_PATCH;
}
S32 LLVersionInfo::getBuild()
U64 LLVersionInfo::getBuild()
{
return LL_VIEWER_VERSION_BUILD;
}

View File

@ -61,7 +61,7 @@ public:
S32 getPatch();
/// return the build number as an integer
S32 getBuild();
U64 getBuild();
/// return the full viewer version as a string like "2.0.0.200030"
std::string getVersion();

View File

@ -160,7 +160,7 @@ std::string LLWeb::expandURLSubstitutions(const std::string &url,
substitution["VERSION_MAJOR"] = LLVersionInfo::instance().getMajor();
substitution["VERSION_MINOR"] = LLVersionInfo::instance().getMinor();
substitution["VERSION_PATCH"] = LLVersionInfo::instance().getPatch();
substitution["VERSION_BUILD"] = LLVersionInfo::instance().getBuild();
substitution["VERSION_BUILD"] = std::to_string(LLVersionInfo::instance().getBuild());
substitution["CHANNEL"] = LLVersionInfo::instance().getChannel();
substitution["GRID"] = LLGridManager::getInstance()->getGridId();
substitution["GRID_LOWERCASE"] = utf8str_tolower(LLGridManager::getInstance()->getGridId());

View File

@ -42,6 +42,7 @@
#include "bufferarray.h"
#include "llversioninfo.h"
#include "llviewercontrol.h"
#include "stringize.h"
// Have to include these last to avoid queue redefinition!
@ -390,14 +391,14 @@ void LLXMLRPCTransaction::Impl::init(XMLRPC_REQUEST request, bool useGzip, const
httpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, HTTP_CONTENT_TEXT_XML);
std::string user_agent = llformat("%s %d.%d.%d (%d)",
LLVersionInfo::instance().getChannel().c_str(),
LLVersionInfo::instance().getMajor(),
LLVersionInfo::instance().getMinor(),
LLVersionInfo::instance().getPatch(),
LLVersionInfo::instance().getBuild());
std::string user_agent = stringize(
LLVersionInfo::instance().getChannel(), ' ',
LLVersionInfo::instance().getMajor(), '.',
LLVersionInfo::instance().getMinor(), '.',
LLVersionInfo::instance().getPatch(), " (",
LLVersionInfo::instance().getBuild(), ')');
httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, user_agent);
httpHeaders->append(HTTP_OUT_HEADER_USER_AGENT, user_agent);
///* Setting the DNS cache timeout to -1 disables it completely.
//This might help with bug #503 */

View File

@ -35,13 +35,13 @@ import os.path
import plistlib
import random
import re
import secrets
import shutil
import stat
import subprocess
import sys
import tarfile
import tempfile
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.
@ -410,11 +410,29 @@ class ViewerManifest(LLManifest):
return os.path.relpath(abspath(path), abspath(base))
def set_github_output_path(self, variable, path):
self.set_github_output(variable,
os.path.normpath(os.path.join(self.get_dst_prefix(), path)))
class WindowsManifest(ViewerManifest):
def set_github_output(self, variable, *values):
GITHUB_OUTPUT = os.getenv('GITHUB_OUTPUT')
if GITHUB_OUTPUT and values:
with open(GITHUB_OUTPUT, 'a') as outf:
if len(values) == 1:
print('='.join((variable, values[0])), file=outf)
else:
delim = secrets.token_hex(8)
print('<<'.join((variable, delim)), file=outf)
for value in values:
print(value, file=outf)
print(delim, file=outf)
class Windows_x86_64_Manifest(ViewerManifest):
# We want the platform, per se, for every Windows build to be 'win'. The
# VMP will concatenate that with the address_size.
build_data_json_platform = 'win'
address_size = 64
def final_exe(self):
return self.exec_name()+".exe"
@ -475,7 +493,7 @@ class WindowsManifest(ViewerManifest):
print("Doesn't exist:", src)
def construct(self):
super(WindowsManifest, self).construct()
super().construct()
pkgdir = os.path.join(self.args['build'], os.pardir, 'packages')
relpkgdir = os.path.join(pkgdir, "lib", "release")
@ -484,6 +502,30 @@ class WindowsManifest(ViewerManifest):
if self.is_packaging_viewer():
# 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())
# Emit the whole app image as one of the GitHub step outputs. We
# want the whole app -- but NOT the extraneous build products that
# get tossed into the same directory, such as the installer and
# the symbols tarball, so add exclusions. When we feed
# upload-artifact multiple absolute pathnames, even just for
# exclusion, it ends up creating several extraneous directory
# levels within the artifact -- so try using only relative paths.
# One problem: as of right now, our current directory os.getcwd()
# is not the same as the initial working directory for this job
# step, meaning paths relative to our os.getcwd() won't work for
# the subsequent upload-artifact step. We're a couple directory
# levels down. Try adjusting for those when specifying the base
# for self.relpath().
appbase = self.relpath(
self.get_dst_prefix(),
base=os.path.join(os.getcwd(), os.pardir, os.pardir))
self.set_github_output('viewer_app', appbase,
# except for this stuff
*(('!' + os.path.join(appbase, pattern))
for pattern in (
'secondlife-bin.*',
'*_Setup.exe',
'*.bat',
'*.tar.bz2')))
with self.prefix(src=os.path.join(pkgdir, "VMP")):
# include the compiled launcher scripts so that it gets included in the file_list
@ -534,20 +576,12 @@ class WindowsManifest(ViewerManifest):
self.path("SLVoice.exe")
# Vivox libraries
if (self.address_size == 64):
self.path("vivoxsdk_x64.dll")
self.path("ortp_x64.dll")
else:
self.path("vivoxsdk.dll")
self.path("ortp.dll")
self.path("vivoxsdk_x64.dll")
self.path("ortp_x64.dll")
# OpenSSL
if (self.address_size == 64):
self.path("libcrypto-1_1-x64.dll")
self.path("libssl-1_1-x64.dll")
else:
self.path("libcrypto-1_1.dll")
self.path("libssl-1_1.dll")
self.path("libcrypto-1_1-x64.dll")
self.path("libssl-1_1-x64.dll")
# HTTP/2
self.path("nghttp2.dll")
@ -557,14 +591,9 @@ class WindowsManifest(ViewerManifest):
# 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")
self.path("BsSndRpt64.exe")
self.path("BugSplat64.dll")
self.path("BugSplatRc64.dll")
self.path(src="licenses-win32.txt", dst="licenses.txt")
self.path("featuretable.txt")
@ -679,46 +708,46 @@ class WindowsManifest(ViewerManifest):
self.package_file = "copied_deps"
def nsi_file_commands(self, install=True):
def wpath(path):
if path.endswith('/') or path.endswith(os.path.sep):
path = path[:-1]
path = path.replace('/', '\\')
return path
def INSTDIR(path):
# Note that '$INSTDIR' is purely textual here: we write
# exactly that into the .nsi file for NSIS to interpret.
# Pass the result through normpath() to handle the case in which
# path is the empty string. On Windows, that produces "$INSTDIR\".
# Unfortunately, if that's the last item on a line, NSIS takes
# that as line continuation and misinterprets the following line.
# Ensure we don't emit a trailing backslash.
return os.path.normpath(os.path.join('$INSTDIR', path))
result = ""
result = []
dest_files = [pair[1] for pair in self.file_list if pair[0] and os.path.isfile(pair[1])]
# sort deepest hierarchy first
dest_files.sort(key=lambda f: (f.count(os.path.sep), f), reverse=True)
out_path = None
for pkg_file in dest_files:
rel_file = os.path.normpath(pkg_file.replace(self.get_dst_prefix()+os.path.sep,''))
installed_dir = wpath(os.path.join('$INSTDIR', os.path.dirname(rel_file)))
pkg_file = wpath(os.path.normpath(pkg_file))
if installed_dir != out_path:
if install:
out_path = installed_dir
result += 'SetOutPath ' + out_path + '\n'
pkg_file = os.path.normpath(pkg_file)
rel_file = self.relpath(pkg_file)
installed_dir = INSTDIR(os.path.dirname(rel_file))
if install and installed_dir != out_path:
out_path = installed_dir
# emit SetOutPath every time it changes
result.append('SetOutPath ' + out_path)
if install:
result += 'File ' + pkg_file + '\n'
result.append('File ' + rel_file)
else:
result += 'Delete ' + wpath(os.path.join('$INSTDIR', rel_file)) + '\n'
result.append('Delete ' + INSTDIR(rel_file))
# at the end of a delete, just rmdir all the directories
if not install:
deleted_file_dirs = [os.path.dirname(pair[1].replace(self.get_dst_prefix()+os.path.sep,'')) for pair in self.file_list]
# find all ancestors so that we don't skip any dirs that happened to have no non-dir children
deleted_dirs = []
for d in deleted_file_dirs:
deleted_dirs.extend(path_ancestors(d))
deleted_file_dirs = [os.path.dirname(self.relpath(f)) for f in dest_files]
# find all ancestors so that we don't skip any dirs that happened
# to have no non-dir children
deleted_dirs = set(itertools.chain.from_iterable(path_ancestors(d)
for d in deleted_file_dirs))
# sort deepest hierarchy first
deleted_dirs.sort(key=lambda f: (f.count(os.path.sep), f), reverse=True)
prev = None
for d in deleted_dirs:
if d != prev: # skip duplicates
result += 'RMDir ' + wpath(os.path.join('$INSTDIR', os.path.normpath(d))) + '\n'
prev = d
for d in sorted(deleted_dirs, key=lambda f: (f.count(os.path.sep), f), reverse=True):
result.append('RMDir ' + INSTDIR(d))
return result
return '\n'.join(result)
def package_finish(self):
# a standard map of strings for replacing in the templates
@ -726,8 +755,7 @@ class WindowsManifest(ViewerManifest):
'version' : '.'.join(self.args['version']),
'version_short' : '.'.join(self.args['version'][:-1]),
'version_dashes' : '-'.join(self.args['version']),
'version_registry' : '%s(%s)' %
('.'.join(self.args['version']), self.address_size),
'version_registry' : '%s(64)' % '.'.join(self.args['version']),
'final_exe' : self.final_exe(),
'flags':'',
'app_name':self.app_name(),
@ -759,75 +787,38 @@ class WindowsManifest(ViewerManifest):
Caption "%(caption)s"
"""
if(self.address_size == 64):
engage_registry="SetRegView 64"
program_files="!define MULTIUSER_USE_PROGRAMFILES64"
else:
engage_registry="SetRegView 32"
program_files=""
engage_registry="SetRegView 64"
program_files="!define MULTIUSER_USE_PROGRAMFILES64"
# Dump the installers/windows directory into the raw app image tree
# because NSIS needs those files. But don't use path() because we
# don't want them installed with the viewer - they're only for use by
# the installer itself.
shutil.copytree(os.path.join(self.get_src_prefix(), 'installers', 'windows'),
os.path.join(self.get_dst_prefix(), 'installers', 'windows'),
dirs_exist_ok=True)
tempfile = "secondlife_setup_tmp.nsi"
# the following replaces strings in the nsi template
# it also does python-style % substitution
self.replace_in("installers/windows/installer_template.nsi", tempfile, {
"%%VERSION%%":version_vars,
"%%SOURCE%%":self.get_src_prefix(),
# The template references "%%SOURCE%%\installers\windows\...".
# Now that we've copied that directory into the app image
# tree, we can just replace %%SOURCE%% with '.'.
"%%SOURCE%%":'.',
"%%INST_VARS%%":inst_vars_template % substitution_strings,
"%%INSTALL_FILES%%":self.nsi_file_commands(True),
"%%PROGRAMFILES%%":program_files,
"%%ENGAGEREGISTRY%%":engage_registry,
"%%DELETE_FILES%%":self.nsi_file_commands(False)})
# If we're on a build machine, sign the code using our Authenticode certificate. JC
# note that the enclosing setup exe is signed later, after the makensis makes it.
# Unlike the viewer binary, the VMP filenames are invariant with respect to version, os, etc.
for exe in (
self.final_exe(),
"SLVersionChecker.exe",
"llplugin/dullahan_host.exe",
):
self.sign(exe)
# Check two paths, one for Program Files, and one for Program Files (x86).
# Yay 64bit windows.
nsis_path = "makensis.exe"
for program_files in '${programfiles}', '${programfiles(x86)}':
for nesis_path in 'NSIS', 'NSIS\\Unicode':
possible_path = os.path.expandvars(f"{program_files}\\{nesis_path}\\makensis.exe")
if os.path.exists(possible_path):
nsis_path = possible_path
break
self.run_command([possible_path, '/V2', self.dst_path_of(tempfile)])
self.sign(installer_file)
self.created_path(self.dst_path_of(installer_file))
self.package_file = installer_file
def sign(self, exe):
sign_py = os.environ.get('SIGN', r'C:\buildscripts\code-signing\sign.py')
python = os.environ.get('PYTHON', sys.executable)
if os.path.exists(sign_py):
dst_path = self.dst_path_of(exe)
print("about to run signing of: ", dst_path)
self.run_command([python, sign_py, dst_path])
else:
print("Skipping code signing of %s %s: %s not found" % (self.dst_path_of(exe), exe, sign_py))
def escape_slashes(self, path):
return path.replace('\\', '\\\\\\\\')
class Windows_i686_Manifest(WindowsManifest):
# Although we aren't literally passed ADDRESS_SIZE, we can infer it from
# the passed 'arch', which is used to select the specific subclass.
address_size = 32
class Windows_x86_64_Manifest(WindowsManifest):
address_size = 64
class DarwinManifest(ViewerManifest):
class Darwin_x86_64_Manifest(ViewerManifest):
build_data_json_platform = 'mac'
address_size = 64
def finish_build_data_dict(self, build_data_dict):
build_data_dict.update({'Bundle Id':self.args['bundleid']})
@ -844,8 +835,9 @@ class DarwinManifest(ViewerManifest):
return bool(set(["package", "unpacked"]).intersection(self.args['actions']))
def construct(self):
# copy over the build result (this is a no-op if run within the xcode script)
self.path(os.path.join(self.args['configuration'], self.channel()+".app"), dst="")
# copy over the build result (this is a no-op if run within the xcode
# script)
self.path(os.path.join(self.args['configuration'], self.channel() + ".app"), dst="")
pkgdir = os.path.join(self.args['build'], os.pardir, 'packages')
relpkgdir = os.path.join(pkgdir, "lib", "release")
@ -898,7 +890,8 @@ class DarwinManifest(ViewerManifest):
# work, we need the build to noisily fail!
oldpath = subprocess.check_output(
['objdump', '--macho', '--dylib-id', '--non-verbose',
os.path.join(relpkgdir, "BugsplatMac.framework", "BugsplatMac")]
os.path.join(relpkgdir, "BugsplatMac.framework", "BugsplatMac")],
text=True
).splitlines()[-1] # take the last line of output
self.run_command(
['install_name_tool', '-change', oldpath,
@ -919,7 +912,7 @@ class DarwinManifest(ViewerManifest):
with self.prefix(dst="Resources"):
# defer cross-platform file copies until we're in the
# nested Resources directory
super(DarwinManifest, self).construct()
super().construct()
# need .icns file referenced by Info.plist
with self.prefix(src=self.icon_path(), dst="") :
@ -1167,194 +1160,35 @@ class DarwinManifest(ViewerManifest):
self.path( "plugins.dat" )
def package_finish(self):
global CHANNEL_VENDOR_BASE
# MBW -- If the mounted volume name changes, it breaks the .DS_Store's background image and icon positioning.
# If we really need differently named volumes, we'll need to create multiple DS_Store file images, or use some other trick.
volname=CHANNEL_VENDOR_BASE+" Installer" # DO NOT CHANGE without understanding comment above
imagename = self.installer_base_name()
sparsename = imagename + ".sparseimage"
self.set_github_output('imagename', imagename)
finalname = imagename + ".dmg"
# make sure we don't have stale files laying about
self.remove(sparsename, finalname)
self.run_command(['hdiutil', 'create', sparsename,
'-volname', volname, '-fs', 'HFS+',
'-type', 'SPARSE', '-megabytes', '1300',
'-layout', 'SPUD'])
# mount the image and get the name of the mount point and device node
try:
hdi_output = subprocess.check_output(['hdiutil', 'attach', '-private', sparsename], text=True)
except subprocess.CalledProcessError as err:
sys.exit("failed to mount image at '%s'" % sparsename)
try:
devfile = re.search("/dev/disk([0-9]+)[^s]", hdi_output).group(0).strip()
volpath = re.search('HFS\s+(.+)', hdi_output).group(1).strip()
# Copy everything in to the mounted .dmg
app_name = self.app_name()
# Hack:
# Because there is no easy way to coerce the Finder into positioning
# the app bundle in the same place with different app names, we are
# adding multiple .DS_Store files to svn. There is one for release,
# one for release candidate and one for first look. Any other channels
# will use the release .DS_Store, and will look broken.
# - Ambroff 2008-08-20
dmg_template = os.path.join(
'installers', 'darwin', '%s-dmg' % self.channel_type())
if not os.path.exists (self.src_path_of(dmg_template)):
dmg_template = os.path.join ('installers', 'darwin', 'release-dmg')
for s,d in list({self.get_dst_prefix():app_name + ".app",
os.path.join(dmg_template, "_VolumeIcon.icns"): ".VolumeIcon.icns",
os.path.join(dmg_template, "background.jpg"): "background.jpg",
os.path.join(dmg_template, "_DS_Store"): ".DS_Store"}.items()):
print("Copying to dmg", s, d)
self.copy_action(self.src_path_of(s), os.path.join(volpath, d))
# Hide the background image, DS_Store file, and volume icon file (set their "visible" bit)
for f in ".VolumeIcon.icns", "background.jpg", ".DS_Store":
pathname = os.path.join(volpath, f)
self.run_command(['SetFile', '-a', 'V', pathname])
# Create the alias file (which is a resource file) from the .r
self.run_command(
['Rez', self.src_path_of("installers/darwin/release-dmg/Applications-alias.r"),
'-o', os.path.join(volpath, "Applications")])
# Set the alias file's alias and custom icon bits
self.run_command(['SetFile', '-a', 'AC', os.path.join(volpath, "Applications")])
# Set the disk image root's custom icon bit
self.run_command(['SetFile', '-a', 'C', volpath])
# Sign the app if requested;
# do this in the copy that's in the .dmg so that the extended attributes used by
# the signature are preserved; moving the files using python will leave them behind
# and invalidate the signatures.
if 'signature' in self.args:
app_in_dmg=os.path.join(volpath,self.app_name()+".app")
print("Attempting to sign '%s'" % app_in_dmg)
identity = self.args['signature']
if identity == '':
identity = 'Developer ID Application'
# Look for an environment variable set via build.sh when running in Team City.
try:
build_secrets_checkout = os.environ['build_secrets_checkout']
except KeyError:
pass
else:
# variable found so use it to unlock keychain followed by codesign
home_path = os.environ['HOME']
keychain_pwd_path = os.path.join(build_secrets_checkout,'code-signing-osx','password.txt')
keychain_pwd = open(keychain_pwd_path).read().rstrip()
# Note: As of macOS Sierra, keychains are created with
# names postfixed with '-db' so for example, the SL
# Viewer keychain would by default be found in
# ~/Library/Keychains/viewer.keychain-db instead of
# just ~/Library/Keychains/viewer.keychain in
# earlier versions.
#
# Because we have old OS files from previous
# versions of macOS on the build hosts, the
# configurations are different on each host. Some
# have viewer.keychain, some have viewer.keychain-db
# and some have both. As you can see in the line
# below, this script expects the Linden Developer
# cert/keys to be in viewer.keychain.
#
# To correctly sign builds you need to make sure
# ~/Library/Keychains/viewer.keychain exists on the
# host and that it contains the correct cert/key. If
# a build host is set up with a clean version of
# macOS Sierra (or later) then you will need to
# change this line (and the one for 'codesign'
# command below) to point to right place or else
# pull in the cert/key into the default viewer
# keychain 'viewer.keychain-db' and export it to
# 'viewer.keychain'
viewer_keychain = os.path.join(home_path, 'Library',
'Keychains', 'viewer.keychain')
self.run_command(['security', 'unlock-keychain',
'-p', keychain_pwd, viewer_keychain])
sign_retry_wait=15
resources = app_in_dmg + "/Contents/Resources/"
plain_sign = glob.glob(resources + "llplugin/*.dylib")
deep_sign = [
resources + "updater/SLVersionChecker",
resources + "SLPlugin.app/Contents/MacOS/SLPlugin",
app_in_dmg,
]
for attempt in range(3):
if attempt: # second or subsequent iteration
print("codesign failed, waiting {:d} seconds before retrying".format(sign_retry_wait),
file=sys.stderr)
time.sleep(sign_retry_wait)
sign_retry_wait*=2
try:
# Note: See blurb above about names of keychains
for signee in plain_sign:
self.run_command(
['codesign',
'--force',
'--timestamp',
'--keychain', viewer_keychain,
'--sign', identity,
signee])
for signee in deep_sign:
self.run_command(
['codesign',
'--verbose',
'--deep',
'--force',
'--entitlements', self.src_path_of("slplugin.entitlements"),
'--options', 'runtime',
'--keychain', viewer_keychain,
'--sign', identity,
signee])
break # if no exception was raised, the codesign worked
except ManifestError as err:
# 'err' goes out of scope
sign_failed = err
else:
print("Maximum codesign attempts exceeded; giving up", file=sys.stderr)
raise sign_failed
self.run_command(['spctl', '-a', '-texec', '-vvvv', app_in_dmg])
self.run_command([self.src_path_of("installers/darwin/apple-notarize.sh"), app_in_dmg])
finally:
# Unmount the image even if exceptions from any of the above
self.run_command(['hdiutil', 'detach', '-force', devfile])
print("Converting temp disk image to final disk image")
self.run_command(['hdiutil', 'convert', sparsename, '-format', 'UDZO',
'-imagekey', 'zlib-level=9', '-o', finalname])
# get rid of the temp file
self.package_file = finalname
self.remove(sparsename)
class Darwin_i386_Manifest(DarwinManifest):
address_size = 32
class Darwin_i686_Manifest(DarwinManifest):
"""alias in case arch is passed as i686 instead of i386"""
pass
class Darwin_x86_64_Manifest(DarwinManifest):
address_size = 64
RUNNER_TEMP = os.getenv('RUNNER_TEMP')
# When running as a GitHub Action job, RUNNER_TEMP is the recommended
# temp directory. If we're not running on GitHub, don't create this
# temp directory or this tarball: we don't clean them up, trusting
# that the runner is itself transient. On a dev machine, that would
# result in temp-directory clutter.
if RUNNER_TEMP:
# Per GitHub's actions/upload-artifact documentation
# https://github.com/actions/upload-artifact#maintaining-file-permissions-and-case-sensitive-files
# we must package the app bundle with tar before posting as an
# artifact. Posting individual files follows symlinks, which
# causes problems, especially with frameworks: a framework's top
# level must contain symlinks into its Versions/Current, which
# must itself be a symlink to some specific Versions subdir.
tarpath = os.path.join(RUNNER_TEMP, "viewer.tar.bz2")
print(f'Creating {tarpath} from {self.get_dst_prefix()}')
with tarfile.open(tarpath, mode="w:bz2") as tarball:
# Store in the tarball as just 'Second Life Mumble.app'
# instead of 'Users/someone/.../newview/Release/Second...'
# It's at this point that we rename 'Second Life Release.app'
# to 'Second Life Viewer.app'.
tarball.add(self.get_dst_prefix(),
arcname=self.app_name() + ".app")
self.set_github_output_path('viewer_app', tarpath)
class LinuxManifest(ViewerManifest):

97
indra/test/hexdump.h Normal file
View File

@ -0,0 +1,97 @@
/**
* @file hexdump.h
* @author Nat Goodspeed
* @date 2023-09-08
* @brief Provide hexdump() and hexmix() ostream formatters
*
* $LicenseInfo:firstyear=2023&license=viewerlgpl$
* Copyright (c) 2023, Linden Research, Inc.
* $/LicenseInfo$
*/
#if ! defined(LL_HEXDUMP_H)
#define LL_HEXDUMP_H
#include <cctype>
#include <iomanip>
#include <iostream>
#include <string_view>
// Format a given byte string as 2-digit hex values, no separators
// Usage: std::cout << hexdump(somestring) << ...
class hexdump
{
public:
hexdump(const std::string_view& data):
hexdump(data.data(), data.length())
{}
hexdump(const char* data, size_t len):
hexdump(reinterpret_cast<const unsigned char*>(data), len)
{}
hexdump(const unsigned char* data, size_t len):
mData(data, data + len)
{}
friend std::ostream& operator<<(std::ostream& out, const hexdump& self)
{
auto oldfmt{ out.flags() };
auto oldfill{ out.fill() };
out.setf(std::ios_base::hex, std::ios_base::basefield);
out.fill('0');
for (auto c : self.mData)
{
out << std::setw(2) << unsigned(c);
}
out.setf(oldfmt, std::ios_base::basefield);
out.fill(oldfill);
return out;
}
private:
std::vector<unsigned char> mData;
};
// Format a given byte string as a mix of printable characters and, for each
// non-printable character, "\xnn"
// Usage: std::cout << hexmix(somestring) << ...
class hexmix
{
public:
hexmix(const std::string_view& data):
mData(data)
{}
hexmix(const char* data, size_t len):
mData(data, len)
{}
friend std::ostream& operator<<(std::ostream& out, const hexmix& self)
{
auto oldfmt{ out.flags() };
auto oldfill{ out.fill() };
out.setf(std::ios_base::hex, std::ios_base::basefield);
out.fill('0');
for (auto c : self.mData)
{
// std::isprint() must be passed an unsigned char!
if (std::isprint(static_cast<unsigned char>(c)))
{
out << c;
}
else
{
out << "\\x" << std::setw(2) << unsigned(c);
}
}
out.setf(oldfmt, std::ios_base::basefield);
out.fill(oldfill);
return out;
}
private:
std::string mData;
};
#endif /* ! defined(LL_HEXDUMP_H) */

View File

@ -13,15 +13,16 @@
#define LL_NAMEDTEMPFILE_H
#include "llerror.h"
#include "llapr.h"
#include "apr_file_io.h"
#include "llstring.h"
#include "stringize.h"
#include <string>
#include <boost/function.hpp>
#include <boost/phoenix/core/argument.hpp>
#include <boost/phoenix/operator/bitwise.hpp>
#include <boost/filesystem.hpp>
#include <boost/filesystem/fstream.hpp>
#include <boost/noncopyable.hpp>
#include <functional>
#include <iostream>
#include <sstream>
#include <string_view>
/**
* Create a text file with specified content "somewhere in the
@ -31,134 +32,123 @@ class NamedTempFile: public boost::noncopyable
{
LOG_CLASS(NamedTempFile);
public:
NamedTempFile(const std::string& pfx, const std::string& content, apr_pool_t* pool=gAPRPoolp):
mPool(pool)
NamedTempFile(const std::string_view& pfx,
const std::string_view& content,
const std::string_view& sfx=std::string_view(""))
{
createFile(pfx, boost::phoenix::placeholders::arg1 << content);
createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx);
}
// Disambiguate when passing string literal
NamedTempFile(const std::string& pfx, const char* content, apr_pool_t* pool=gAPRPoolp):
mPool(pool)
// Disambiguate when passing string literal -- unclear why a string
// literal should be ambiguous wrt std::string_view and Streamer
NamedTempFile(const std::string_view& pfx,
const char* content,
const std::string_view& sfx=std::string_view(""))
{
createFile(pfx, boost::phoenix::placeholders::arg1 << content);
createFile(pfx, [&content](std::ostream& out){ out << content; }, sfx);
}
// Function that accepts an ostream ref and (presumably) writes stuff to
// it, e.g.:
// (boost::phoenix::placeholders::arg1 << "the value is " << 17 << '\n')
typedef boost::function<void(std::ostream&)> Streamer;
typedef std::function<void(std::ostream&)> Streamer;
NamedTempFile(const std::string& pfx, const Streamer& func, apr_pool_t* pool=gAPRPoolp):
mPool(pool)
NamedTempFile(const std::string_view& pfx,
const Streamer& func,
const std::string_view& sfx=std::string_view(""))
{
createFile(pfx, func);
createFile(pfx, func, sfx);
}
virtual ~NamedTempFile()
{
ll_apr_assert_status(apr_file_remove(mPath.c_str(), mPool));
boost::filesystem::remove(mPath);
}
virtual std::string getName() const { return mPath; }
std::string getName() const { return mPath.string(); }
void peep()
template <typename CALLABLE>
void peep_via(CALLABLE&& callable) const
{
std::cout << "File '" << mPath << "' contains:\n";
std::ifstream reader(mPath.c_str());
std::forward<CALLABLE>(callable)(stringize("File '", mPath, "' contains:"));
boost::filesystem::ifstream reader(mPath, std::ios::binary);
std::string line;
while (std::getline(reader, line))
std::cout << line << '\n';
std::cout << "---\n";
std::forward<CALLABLE>(callable)(line);
std::forward<CALLABLE>(callable)("---");
}
void peep_log() const
{
peep_via([](const std::string& line){ LL_DEBUGS() << line << LL_ENDL; });
}
void peep(std::ostream& out=std::cout) const
{
peep_via([&out](const std::string& line){ out << line << '\n'; });
}
friend std::ostream& operator<<(std::ostream& out, const NamedTempFile& self)
{
self.peep(out);
return out;
}
static boost::filesystem::path temp_path(const std::string_view& pfx="",
const std::string_view& sfx="")
{
// This variable is set by GitHub actions and is the recommended place
// to put temp files belonging to an actions job.
const char* RUNNER_TEMP = getenv("RUNNER_TEMP");
boost::filesystem::path tempdir{
// if RUNNER_TEMP is set and not empty
(RUNNER_TEMP && *RUNNER_TEMP)?
boost::filesystem::path(RUNNER_TEMP) : // use RUNNER_TEMP if available
boost::filesystem::temp_directory_path()}; // else canonical temp dir
boost::filesystem::path tempname{
// use filename template recommended by unique_path() doc, but
// with underscores instead of hyphens: some use cases involve
// temporary Python scripts
tempdir / stringize(pfx, "%%%%_%%%%_%%%%_%%%%", sfx) };
return boost::filesystem::unique_path(tempname);
}
protected:
void createFile(const std::string& pfx, const Streamer& func)
void createFile(const std::string_view& pfx,
const Streamer& func,
const std::string_view& sfx)
{
// Create file in a temporary place.
const char* tempdir = NULL;
ll_apr_assert_status(apr_temp_dir_get(&tempdir, mPool));
// Construct a temp filename template in that directory.
char *tempname = NULL;
ll_apr_assert_status(apr_filepath_merge(&tempname,
tempdir,
(pfx + "XXXXXX").c_str(),
0,
mPool));
// Create a temp file from that template.
apr_file_t* fp = NULL;
ll_apr_assert_status(apr_file_mktemp(&fp,
tempname,
APR_CREATE | APR_WRITE | APR_EXCL,
mPool));
// apr_file_mktemp() alters tempname with the actual name. Not until
// now is it valid to capture as our mPath.
mPath = tempname;
mPath = temp_path(pfx, sfx);
boost::filesystem::ofstream out{ mPath, std::ios::binary };
// Write desired content.
std::ostringstream out;
// Stream stuff to it.
func(out);
std::string data(out.str());
apr_size_t writelen(data.length());
ll_apr_assert_status(apr_file_write(fp, data.c_str(), &writelen));
ll_apr_assert_status(apr_file_close(fp));
llassert_always(writelen == data.length());
}
std::string mPath;
apr_pool_t* mPool;
boost::filesystem::path mPath;
};
/**
* Create a NamedTempFile with a specified filename extension. This is useful
* when, for instance, you must be able to use the file in a Python import
* statement.
*
* A NamedExtTempFile actually has two different names. We retain the original
* no-extension name as a placeholder in the temp directory to ensure
* uniqueness; to that we link the name plus the desired extension. Naturally,
* both must be removed on destruction.
*/
class NamedExtTempFile: public NamedTempFile
{
LOG_CLASS(NamedExtTempFile);
public:
NamedExtTempFile(const std::string& ext, const std::string& content, apr_pool_t* pool=gAPRPoolp):
NamedTempFile(remove_dot(ext), content, pool),
mLink(mPath + ensure_dot(ext))
{
linkto(mLink);
}
NamedExtTempFile(const std::string& ext, const std::string_view& content):
NamedTempFile(remove_dot(ext), content, ensure_dot(ext))
{}
// Disambiguate when passing string literal
NamedExtTempFile(const std::string& ext, const char* content, apr_pool_t* pool=gAPRPoolp):
NamedTempFile(remove_dot(ext), content, pool),
mLink(mPath + ensure_dot(ext))
{
linkto(mLink);
}
NamedExtTempFile(const std::string& ext, const char* content):
NamedTempFile(remove_dot(ext), content, ensure_dot(ext))
{}
NamedExtTempFile(const std::string& ext, const Streamer& func, apr_pool_t* pool=gAPRPoolp):
NamedTempFile(remove_dot(ext), func, pool),
mLink(mPath + ensure_dot(ext))
{
linkto(mLink);
}
virtual ~NamedExtTempFile()
{
ll_apr_assert_status(apr_file_remove(mLink.c_str(), mPool));
}
// Since the caller has gone to the trouble to create the name with the
// extension, that should be the name we return. In this class, mPath is
// just a placeholder to ensure that future createFile() calls won't
// collide.
virtual std::string getName() const { return mLink; }
NamedExtTempFile(const std::string& ext, const Streamer& func):
NamedTempFile(remove_dot(ext), func, ensure_dot(ext))
{}
static std::string ensure_dot(const std::string& ext)
{
@ -175,7 +165,7 @@ public:
{
return ext;
}
return std::string(".") + ext;
return "." + ext;
}
static std::string remove_dot(const std::string& ext)
@ -187,19 +177,6 @@ public:
}
return ext.substr(found);
}
private:
void linkto(const std::string& path)
{
// This method assumes that since mPath (without extension) is
// guaranteed by apr_file_mktemp() to be unique, then (mPath + any
// extension) is also unique. This is likely, though not guaranteed:
// files could be created in the same temp directory other than by
// this class.
ll_apr_assert_status(apr_file_link(mPath.c_str(), path.c_str()));
}
std::string mLink;
};
#endif /* ! defined(LL_NAMEDTEMPFILE_H) */

View File

@ -97,10 +97,10 @@ public:
class RecordToTempFile : public LLError::Recorder, public boost::noncopyable
{
public:
RecordToTempFile(apr_pool_t* pPool)
RecordToTempFile()
: LLError::Recorder(),
boost::noncopyable(),
mTempFile("log", "", pPool),
mTempFile("log", ""),
mFile(mTempFile.getName().c_str())
{
}
@ -141,11 +141,11 @@ private:
class LLReplayLogReal: public LLReplayLog, public boost::noncopyable
{
public:
LLReplayLogReal(LLError::ELevel level, apr_pool_t* pool)
LLReplayLogReal(LLError::ELevel level)
: LLReplayLog(),
boost::noncopyable(),
mOldSettings(LLError::saveAndResetSettings()),
mRecorder(new RecordToTempFile(pool))
mRecorder(new RecordToTempFile())
{
LLError::setFatalFunction(wouldHaveCrashed);
LLError::setDefaultLevel(level);
@ -624,7 +624,7 @@ int main(int argc, char **argv)
if (LOGFAIL && *LOGFAIL)
{
LLError::ELevel level = LLError::decodeLevel(LOGFAIL);
replayer.reset(new LLReplayLogReal(level, gAPRPoolp));
replayer.reset(new LLReplayLogReal(level));
}
}
LLError::setFatalFunction(wouldHaveCrashed);