summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorAndrey Lihatskiy <alihatskiy@productengine.com>2023-10-25 21:28:26 +0300
committerAndrey Lihatskiy <alihatskiy@productengine.com>2023-10-25 21:28:26 +0300
commit4c6ae49c88f58d04ae89ffb444954e12f60e0d52 (patch)
treeacb8ab3be3052138ea2186d497b0991604ce068e /indra
parenta97d50548090f181f8156ab41a5e03b3e383c1c8 (diff)
parent2e8e96cfbcb383a667d1b938f364f0bbafcad5b4 (diff)
Merge branch 'main' into DRTVWR-587-maint-V
# Conflicts: # autobuild.xml # indra/llcommon/tests/llleap_test.cpp # indra/newview/viewer_manifest.py
Diffstat (limited to 'indra')
-rw-r--r--indra/cmake/00-Common.cmake14
-rw-r--r--indra/cmake/APR.cmake2
-rw-r--r--indra/cmake/Copy3rdPartyLibs.cmake2
-rw-r--r--indra/cmake/Linking.cmake1
-rw-r--r--indra/cmake/Variables.cmake18
-rwxr-xr-xindra/lib/python/indra/util/llmanifest.py7
-rwxr-xr-xindra/llaudio/llaudioengine.h1
-rw-r--r--indra/llcommon/llcoros.cpp6
-rw-r--r--indra/llcommon/llerror.cpp17
-rw-r--r--indra/llcommon/llerror.h10
-rw-r--r--indra/llcommon/llleap.cpp11
-rw-r--r--indra/llcommon/llprocessor.cpp2
-rw-r--r--indra/llcommon/llrand.cpp104
-rw-r--r--indra/llcommon/tests/llleap_test.cpp260
-rw-r--r--indra/llcommon/tests/llprocess_test.cpp74
-rw-r--r--indra/llcommon/tests/llrand_test.cpp53
-rw-r--r--indra/llcommon/tests/llsdserialize_test.cpp133
-rw-r--r--indra/llcommon/tests/workqueue_test.cpp6
-rw-r--r--indra/llinventory/llsettingsbase.cpp1
-rw-r--r--indra/llinventory/llsettingssky.cpp1
-rw-r--r--indra/llinventory/llsettingswater.cpp1
-rwxr-xr-xindra/llmessage/tests/test_llsdmessage_peer.py1
-rw-r--r--indra/newview/CMakeLists.txt24
-rw-r--r--indra/newview/installers/windows/installer_template.nsi1
-rw-r--r--indra/newview/llappviewer.cpp8
-rw-r--r--indra/newview/llcurrencyuimanager.cpp13
-rw-r--r--indra/newview/llenvironment.h2
-rw-r--r--indra/newview/llimview.cpp2
-rw-r--r--indra/newview/llinventorygallery.cpp2
-rw-r--r--indra/newview/lllogchat.cpp2
-rw-r--r--indra/newview/llpanellogin.cpp13
-rw-r--r--indra/newview/lltranslate.cpp25
-rw-r--r--indra/newview/llversioninfo.cpp4
-rw-r--r--indra/newview/llversioninfo.h2
-rw-r--r--indra/newview/llweb.cpp2
-rw-r--r--indra/newview/llxmlrpctransaction.cpp15
-rwxr-xr-xindra/newview/viewer_manifest.py424
-rw-r--r--indra/test/hexdump.h97
-rw-r--r--indra/test/namedtempfile.h183
-rw-r--r--indra/test/test.cpp10
40 files changed, 751 insertions, 803 deletions
diff --git a/indra/cmake/00-Common.cmake b/indra/cmake/00-Common.cmake
index c708199309..1207979762 100644
--- a/indra/cmake/00-Common.cmake
+++ b/indra/cmake/00-Common.cmake
@@ -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
diff --git a/indra/cmake/APR.cmake b/indra/cmake/APR.cmake
index 8a0939c92c..21139319c3 100644
--- a/indra/cmake/APR.cmake
+++ b/indra/cmake/APR.cmake
@@ -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
)
diff --git a/indra/cmake/Copy3rdPartyLibs.cmake b/indra/cmake/Copy3rdPartyLibs.cmake
index d43cc30706..a3db02372d 100644
--- a/indra/cmake/Copy3rdPartyLibs.cmake
+++ b/indra/cmake/Copy3rdPartyLibs.cmake
@@ -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
diff --git a/indra/cmake/Linking.cmake b/indra/cmake/Linking.cmake
index 4a501f420b..1ce21c11f9 100644
--- a/indra/cmake/Linking.cmake
+++ b/indra/cmake/Linking.cmake
@@ -62,6 +62,7 @@ elseif (WINDOWS)
user32
ole32
dbghelp
+ rpcrt4.lib
legacy_stdio_definitions
)
else()
diff --git a/indra/cmake/Variables.cmake b/indra/cmake/Variables.cmake
index e6285ab48b..778384f87c 100644
--- a/indra/cmake/Variables.cmake
+++ b/indra/cmake/Variables.cmake
@@ -173,13 +173,17 @@ if (${CMAKE_SYSTEM_NAME} MATCHES "Darwin")
set(CMAKE_XCODE_ATTRIBUTE_GCC_OPTIMIZATION_LEVEL "${CMAKE_MATCH_1}")
message(STATUS "CMAKE_XCODE_ATTRIBUTE_GCC_OPTIMIZATION_LEVEL = '${CMAKE_XCODE_ATTRIBUTE_GCC_OPTIMIZATION_LEVEL}'")
- string(REGEX MATCHALL "[^ ]+" LL_BUILD_LIST "$ENV{LL_BUILD}")
- list(FIND LL_BUILD_LIST "-iwithsysroot" sysroot_idx)
- if ("${sysroot_idx}" LESS 0)
- message(FATAL_ERROR "Environment variable LL_BUILD must contain '-iwithsysroot'")
- endif ()
- math(EXPR sysroot_idx "${sysroot_idx} + 1")
- list(GET LL_BUILD_LIST "${sysroot_idx}" CMAKE_OSX_SYSROOT)
+ # allow disabling this check by setting LL_SKIP_REQUIRE_SYSROOT either ON as cmake cache var or non-empty as environment var
+ set(LL_SKIP_REQUIRE_SYSROOT OFF CACHE BOOL "Skip requirement to set toolchain sysroot ahead of time. Not skipped by default for consistency, but skipping can be useful for selecting alternative xcode versions side by side")
+ if("$ENV{LL_SKIP_REQUIRE_SYSROOT}" STREQUAL "" AND NOT ${LL_SKIP_REQUIRE_SYSROOT})
+ string(REGEX MATCHALL "[^ ]+" LL_BUILD_LIST "$ENV{LL_BUILD}")
+ list(FIND LL_BUILD_LIST "-iwithsysroot" sysroot_idx)
+ if ("${sysroot_idx}" LESS 0)
+ message(FATAL_ERROR "Environment variable LL_BUILD must contain '-iwithsysroot'")
+ endif ()
+ math(EXPR sysroot_idx "${sysroot_idx} + 1")
+ list(GET LL_BUILD_LIST "${sysroot_idx}" CMAKE_OSX_SYSROOT)
+ endif()
message(STATUS "CMAKE_OSX_SYSROOT = '${CMAKE_OSX_SYSROOT}'")
set(CMAKE_XCODE_ATTRIBUTE_GCC_VERSION "com.apple.compilers.llvm.clang.1_0")
diff --git a/indra/lib/python/indra/util/llmanifest.py b/indra/lib/python/indra/util/llmanifest.py
index 820f356dae..bcb9d884c3 100755
--- a/indra/lib/python/indra/util/llmanifest.py
+++ b/indra/lib/python/indra/util/llmanifest.py
@@ -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) )
diff --git a/indra/llaudio/llaudioengine.h b/indra/llaudio/llaudioengine.h
index a133898969..c045d18c42 100755
--- a/indra/llaudio/llaudioengine.h
+++ b/indra/llaudio/llaudioengine.h
@@ -30,6 +30,7 @@
#include <list>
#include <map>
+#include <array>
#include "v3math.h"
#include "v3dmath.h"
diff --git a/indra/llcommon/llcoros.cpp b/indra/llcommon/llcoros.cpp
index fe2adf051b..3ab97b557f 100644
--- a/indra/llcommon/llcoros.cpp
+++ b/indra/llcommon/llcoros.cpp
@@ -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*){} }
diff --git a/indra/llcommon/llerror.cpp b/indra/llcommon/llerror.cpp
index 02cb186275..05e719b494 100644
--- a/indra/llcommon/llerror.cpp
+++ b/indra/llcommon/llerror.cpp
@@ -1617,5 +1617,18 @@ bool debugLoggingEnabled(const std::string& tag)
return res;
}
-
-
+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);
+}
diff --git a/indra/llcommon/llerror.h b/indra/llcommon/llerror.h
index 020f05e8f5..624a5fb37a 100644
--- a/indra/llcommon/llerror.h
+++ b/indra/llcommon/llerror.h
@@ -383,11 +383,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 \
@@ -466,5 +464,7 @@ typedef LLError::NoClassInfo _LL_CLASS_TO_LOG;
// Check at run-time whether logging is enabled, without generating output
bool debugLoggingEnabled(const std::string& tag);
+// used by LLERROR_CRASH
+void crashdriver(void (*)(int*));
#endif // LL_LLERROR_H
diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp
index e0ac807169..8f88e728ce 100644
--- a/indra/llcommon/llleap.cpp
+++ b/indra/llcommon/llleap.cpp
@@ -406,6 +406,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;
}
diff --git a/indra/llcommon/llprocessor.cpp b/indra/llcommon/llprocessor.cpp
index 4a1a81f083..28f8bc2b93 100644
--- a/indra/llcommon/llprocessor.cpp
+++ b/indra/llcommon/llprocessor.cpp
@@ -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
diff --git a/indra/llcommon/llrand.cpp b/indra/llcommon/llrand.cpp
index cb28a8f5c3..33afc50cf7 100644
--- a/indra/llcommon/llrand.cpp
+++ b/indra/llcommon/llrand.cpp
@@ -58,46 +58,14 @@
* to restore uniform distribution.
*/
-// *NOTE: The system rand implementation is probably not correct.
-#define LL_USE_SYSTEM_RAND 0
+static LLRandLagFib2281 gRandomGenerator(LLUUID::getRandomSeed());
-#if LL_USE_SYSTEM_RAND
-#include <cstdlib>
-#endif
+// no default implementation, only specific F64 and F32 specializations
+template <typename REAL>
+inline REAL ll_internal_random();
-#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()
+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>();
+}
+
+F32 ll_frand()
+{
+ return ll_internal_random_float();
}
-#endif
+/*-------------------------- 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()
-{
- return ll_internal_random_float();
-}
-
-F32 ll_frand(F32 val)
+template <typename REAL>
+REAL ll_grand(REAL val)
{
// The clamping rules are described above.
- F32 rv = ll_internal_random_float() * val;
+ REAL rv = ll_internal_random<REAL>() * val;
if(val > 0)
{
- if(rv >= val) return 0.0f;
+ if(rv >= val) return REAL();
}
else
{
- if(rv <= val) return 0.0f;
+ if(rv <= val) return REAL();
}
return rv;
}
-F64 ll_drand()
+F32 ll_frand(F32 val)
{
- 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);
}
diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp
index 48e14ad7a5..7197dedfbf 100644
--- a/indra/llcommon/tests/llleap_test.cpp
+++ b/indra/llcommon/tests/llleap_test.cpp
@@ -17,9 +17,6 @@
// std headers
#include <functional>
// external library headers
-//#include <boost/algorithm/string/join.hpp>
-#include <boost/assign/list_of.hpp>
-#include <boost/phoenix/core/argument.hpp>
// other Linden headers
#include "../test/lltut.h"
#include "../test/namedtempfile.h"
@@ -31,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)
@@ -105,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"
@@ -194,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
@@ -219,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.
@@ -237,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));
@@ -252,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!");
}
@@ -265,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!");
}
@@ -280,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:");
}
@@ -387,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();
}
@@ -425,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();
}
@@ -468,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();
}
diff --git a/indra/llcommon/tests/llprocess_test.cpp b/indra/llcommon/tests/llprocess_test.cpp
index 81449b4a42..b6b297b8d7 100644
--- a/indra/llcommon/tests/llprocess_test.cpp
+++ b/indra/llcommon/tests/llprocess_test.cpp
@@ -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<>
diff --git a/indra/llcommon/tests/llrand_test.cpp b/indra/llcommon/tests/llrand_test.cpp
index 383e6f9e0a..ac5a33d0ba 100644
--- a/indra/llcommon/tests/llrand_test.cpp
+++ b/indra/llcommon/tests/llrand_test.cpp
@@ -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);
}
}
}
diff --git a/indra/llcommon/tests/llsdserialize_test.cpp b/indra/llcommon/tests/llsdserialize_test.cpp
index acb2953b5b..ac40125f75 100644
--- a/indra/llcommon/tests/llsdserialize_test.cpp
+++ b/indra/llcommon/tests/llsdserialize_test.cpp
@@ -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;
diff --git a/indra/llcommon/tests/workqueue_test.cpp b/indra/llcommon/tests/workqueue_test.cpp
index 1d73f7aa0d..7655a7aa1f 100644
--- a/indra/llcommon/tests/workqueue_test.cpp
+++ b/indra/llcommon/tests/workqueue_test.cpp
@@ -83,7 +83,11 @@ namespace tut
// signal the work item that it can quit; consider LLOneShotCond.
LLCond<Shared> data;
auto start = WorkQueue::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]
diff --git a/indra/llinventory/llsettingsbase.cpp b/indra/llinventory/llsettingsbase.cpp
index 936b166409..6ea93e045d 100644
--- a/indra/llinventory/llsettingsbase.cpp
+++ b/indra/llinventory/llsettingsbase.cpp
@@ -31,6 +31,7 @@
#include <algorithm>
#include "llsdserialize.h"
+#include <boost/bind.hpp>
//=========================================================================
namespace
diff --git a/indra/llinventory/llsettingssky.cpp b/indra/llinventory/llsettingssky.cpp
index a129f0a6f0..51fca76518 100644
--- a/indra/llinventory/llsettingssky.cpp
+++ b/indra/llinventory/llsettingssky.cpp
@@ -31,6 +31,7 @@
#include "lltrace.h"
#include "llfasttimer.h"
#include "v3colorutil.h"
+#include <boost/bind.hpp>
//=========================================================================
namespace
diff --git a/indra/llinventory/llsettingswater.cpp b/indra/llinventory/llsettingswater.cpp
index d732032a6c..89156000b0 100644
--- a/indra/llinventory/llsettingswater.cpp
+++ b/indra/llinventory/llsettingswater.cpp
@@ -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");
diff --git a/indra/llmessage/tests/test_llsdmessage_peer.py b/indra/llmessage/tests/test_llsdmessage_peer.py
index 8e9b6c09e7..ff8f40a144 100755
--- a/indra/llmessage/tests/test_llsdmessage_peer.py
+++ b/indra/llmessage/tests/test_llsdmessage_peer.py
@@ -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
diff --git a/indra/newview/CMakeLists.txt b/indra/newview/CMakeLists.txt
index c37d03221c..2c45e0713c 100644
--- a/indra/newview/CMakeLists.txt
+++ b/indra/newview/CMakeLists.txt
@@ -2151,20 +2151,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
@@ -2182,24 +2168,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)
diff --git a/indra/newview/installers/windows/installer_template.nsi b/indra/newview/installers/windows/installer_template.nsi
index ed37f541c8..962d6c060c 100644
--- a/indra/newview/installers/windows/installer_template.nsi
+++ b/indra/newview/installers/windows/installer_template.nsi
@@ -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
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index 38ad6df60c..4bab51a1f5 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -3210,8 +3210,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;
@@ -3558,7 +3560,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();
@@ -5517,7 +5519,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])
diff --git a/indra/newview/llcurrencyuimanager.cpp b/indra/newview/llcurrencyuimanager.cpp
index 232e461fd0..4c0a5cf183 100644
--- a/indra/newview/llcurrencyuimanager.cpp
+++ b/indra/newview/llcurrencyuimanager.cpp
@@ -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);
diff --git a/indra/newview/llenvironment.h b/indra/newview/llenvironment.h
index 64fd170e43..1c8a68ae02 100644
--- a/indra/newview/llenvironment.h
+++ b/indra/newview/llenvironment.h
@@ -42,6 +42,8 @@
#include <boost/signals2.hpp>
+#include <array>
+
//-------------------------------------------------------------------------
class LLViewerCamera;
class LLParcel;
diff --git a/indra/newview/llimview.cpp b/indra/newview/llimview.cpp
index 6880cf2171..61a01d7418 100644
--- a/indra/newview/llimview.cpp
+++ b/indra/newview/llimview.cpp
@@ -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");
diff --git a/indra/newview/llinventorygallery.cpp b/indra/newview/llinventorygallery.cpp
index 4838ba7a47..845ea01f56 100644
--- a/indra/newview/llinventorygallery.cpp
+++ b/indra/newview/llinventorygallery.cpp
@@ -2420,6 +2420,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);
}
diff --git a/indra/newview/lllogchat.cpp b/indra/newview/lllogchat.cpp
index ba82ff0b0f..8c03292361 100644
--- a/indra/newview/lllogchat.cpp
+++ b/indra/newview/lllogchat.cpp
@@ -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
diff --git a/indra/newview/llpanellogin.cpp b/indra/newview/llpanellogin.cpp
index 49756a4e09..025a653c47 100644
--- a/indra/newview/llpanellogin.cpp
+++ b/indra/newview/llpanellogin.cpp
@@ -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
diff --git a/indra/newview/lltranslate.cpp b/indra/newview/lltranslate.cpp
index c37c955e8d..6526e1df92 100644
--- a/indra/newview/lltranslate.cpp
+++ b/indra/newview/lltranslate.cpp
@@ -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);
diff --git a/indra/newview/llversioninfo.cpp b/indra/newview/llversioninfo.cpp
index 376a7fce76..9551df7bee 100644
--- a/indra/newview/llversioninfo.cpp
+++ b/indra/newview/llversioninfo.cpp
@@ -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;
}
diff --git a/indra/newview/llversioninfo.h b/indra/newview/llversioninfo.h
index 02ff0c094a..a40042380a 100644
--- a/indra/newview/llversioninfo.h
+++ b/indra/newview/llversioninfo.h
@@ -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();
diff --git a/indra/newview/llweb.cpp b/indra/newview/llweb.cpp
index c4d873dd22..9afe332025 100644
--- a/indra/newview/llweb.cpp
+++ b/indra/newview/llweb.cpp
@@ -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());
diff --git a/indra/newview/llxmlrpctransaction.cpp b/indra/newview/llxmlrpctransaction.cpp
index 8d178dbbdc..8ea07fcee0 100644
--- a/indra/newview/llxmlrpctransaction.cpp
+++ b/indra/newview/llxmlrpctransaction.cpp
@@ -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!
@@ -384,14 +385,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 */
diff --git a/indra/newview/viewer_manifest.py b/indra/newview/viewer_manifest.py
index 1b4a5545bd..1fa4df1682 100755
--- a/indra/newview/viewer_manifest.py
+++ b/indra/newview/viewer_manifest.py
@@ -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
-
- result = ""
+ 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 = []
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 bit
- self.run_command(['SetFile', '-a', 'A', 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):
diff --git a/indra/test/hexdump.h b/indra/test/hexdump.h
new file mode 100644
index 0000000000..dd7cbaaa3c
--- /dev/null
+++ b/indra/test/hexdump.h
@@ -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) */
diff --git a/indra/test/namedtempfile.h b/indra/test/namedtempfile.h
index 7d59cad32c..ad14cebbd1 100644
--- a/indra/test/namedtempfile.h
+++ b/indra/test/namedtempfile.h
@@ -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 Streamer& func, apr_pool_t* pool=gAPRPoolp):
- NamedTempFile(remove_dot(ext), func, pool),
- mLink(mPath + ensure_dot(ext))
- {
- linkto(mLink);
- }
+ NamedExtTempFile(const std::string& ext, const char* content):
+ NamedTempFile(remove_dot(ext), content, ensure_dot(ext))
+ {}
- 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) */
diff --git a/indra/test/test.cpp b/indra/test/test.cpp
index 1161a6d8e4..611eeb43b6 100644
--- a/indra/test/test.cpp
+++ b/indra/test/test.cpp
@@ -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);