summaryrefslogtreecommitdiff
path: root/indra
diff options
context:
space:
mode:
authorNat Goodspeed <nat@lindenlab.com>2024-09-23 17:24:16 -0400
committerNat Goodspeed <nat@lindenlab.com>2024-09-23 17:24:16 -0400
commit74badfcfb07733214cbe5aa5ae02bd413f8465a2 (patch)
treef1dabff03453d87a78257d872acddb8f86f4da9c /indra
parente290d14f65946600939321d2471ce9d4b2508e3e (diff)
parent9289b96de48e0a3f57819ca173c5d5d51ad25c56 (diff)
Merge remote 'release/luau-scripting' into release/luau-scripting
Diffstat (limited to 'indra')
-rw-r--r--indra/llcommon/CMakeLists.txt2
-rw-r--r--indra/llcommon/llleap.cpp45
-rw-r--r--indra/llcommon/lua_function.cpp200
-rw-r--r--indra/llcommon/lua_function.h21
-rw-r--r--indra/llcommon/scriptcommand.cpp117
-rw-r--r--indra/llcommon/scriptcommand.h64
-rw-r--r--indra/llcommon/tests/llleap_test.cpp4
-rw-r--r--indra/newview/llappviewer.cpp26
-rw-r--r--indra/newview/llluamanager.cpp62
-rw-r--r--indra/newview/llluamanager.h22
-rw-r--r--indra/newview/scripts/lua/frame_profile_quit.lua17
-rw-r--r--indra/newview/scripts/lua/require/login.lua11
12 files changed, 429 insertions, 162 deletions
diff --git a/indra/llcommon/CMakeLists.txt b/indra/llcommon/CMakeLists.txt
index eaf32f68ea..78bfaade55 100644
--- a/indra/llcommon/CMakeLists.txt
+++ b/indra/llcommon/CMakeLists.txt
@@ -108,6 +108,7 @@ set(llcommon_SOURCE_FILES
lua_function.cpp
lualistener.cpp
resultset.cpp
+ scriptcommand.cpp
threadpool.cpp
throttle.cpp
u64.cpp
@@ -256,6 +257,7 @@ set(llcommon_HEADER_FILES
lua_function.h
lualistener.h
resultset.h
+ scriptcommand.h
stdtypes.h
stringize.h
tempset.h
diff --git a/indra/llcommon/llleap.cpp b/indra/llcommon/llleap.cpp
index 306d4e48d0..ef43af6ceb 100644
--- a/indra/llcommon/llleap.cpp
+++ b/indra/llcommon/llleap.cpp
@@ -21,17 +21,18 @@
// external library headers
// other Linden headers
#include "llerror.h"
-#include "llstring.h"
-#include "llprocess.h"
+#include "llerrorcontrol.h"
#include "llevents.h"
-#include "stringize.h"
-#include "llsdutil.h"
+#include "llexception.h"
+#include "llleaplistener.h"
+#include "llprocess.h"
#include "llsdserialize.h"
-#include "llerrorcontrol.h"
+#include "llsdutil.h"
+#include "llstring.h"
#include "lltimer.h"
#include "lluuid.h"
-#include "llleaplistener.h"
-#include "llexception.h"
+#include "scriptcommand.h"
+#include "stringize.h"
#if LL_MSVC
#pragma warning (disable : 4355) // 'this' used in initializer list: yes, intentionally
@@ -107,7 +108,7 @@ public:
// If that didn't work, no point in keeping this LLLeap object.
if (! mChild)
{
- LLTHROW(Error(STRINGIZE("failed to run " << mDesc)));
+ LLTHROW(Error(stringize("failed to run ", mDesc)));
}
// Okay, launch apparently worked. Change our mDonePump listener.
@@ -484,12 +485,24 @@ LLLeap* LLLeap::create(const std::string& desc, const std::vector<std::string>&
LLLeap* LLLeap::create(const std::string& desc, const std::string& plugin, bool exc)
{
- // Use LLStringUtil::getTokens() to parse the command line
- return create(desc,
- LLStringUtil::getTokens(plugin,
- " \t\r\n", // drop_delims
- "", // no keep_delims
- "\"'", // either kind of quotes
- "\\"), // backslash escape
- exc);
+ // Use ScriptCommand to parse the command line
+ ScriptCommand command(plugin);
+ auto error = command.error();
+ if (! error.empty())
+ {
+ if (exc)
+ {
+ LLTHROW(Error(error));
+ }
+ return nullptr;
+ }
+
+ LLProcess::Params params;
+ params.desc = desc;
+ params.executable = command.script;
+ for (const auto& arg : command.args)
+ {
+ params.args.add(arg);
+ }
+ return create(params, exc);
}
diff --git a/indra/llcommon/lua_function.cpp b/indra/llcommon/lua_function.cpp
index 2557fd0cc9..a9f88f3170 100644
--- a/indra/llcommon/lua_function.cpp
+++ b/indra/llcommon/lua_function.cpp
@@ -60,8 +60,11 @@ namespace
namespace lluau
{
-int dostring(lua_State* L, const std::string& desc, const std::string& text)
+int dostring(lua_State* L, const std::string& desc, const std::string& text,
+ const std::vector<std::string>& args)
{
+ // debug.traceback() + compiled chunk + args table + args... + slop
+ lluau_checkstack(L, 1 + 1 + 1 + int(args.size()) + 2);
auto r = loadstring(L, desc, text);
if (r != LUA_OK)
return r;
@@ -80,16 +83,71 @@ int dostring(lua_State* L, const std::string& desc, const std::string& text)
// stack: compiled chunk, debug.traceback()
lua_insert(L, -2);
// stack: debug.traceback(), compiled chunk
- LuaRemover cleanup(L, -2);
+ // capture absolute index of debug.traceback()
+ int traceback = lua_absindex(L, -2);
+ // remove it from stack on exit
+ LuaRemover cleanup(L, traceback);
+
+ // Originally we just pushed 'args' to the Lua stack before entering the
+ // chunk. But that's awkward for the chunk: it must reference those
+ // arguments as '...', using any of a number of tactics to move them to
+ // named variables. This doesn't work as a Lua chunk expecting arguments:
+ // function(a, b, c)
+ // -- ...
+ // end
+ // because that code only *defines* a function: the function's body isn't
+ // entered by executing the chunk.
+ //
+ // Per https://www.lua.org/manual/5.1/manual.html#6 Lua Stand-alone, we
+ // now also create a global table called 'arg' whose [0] is the script
+ // name, ['n'] is the number of additional arguments and [1] through
+ // [['n']] are the additional arguments. We diverge from that spec in not
+ // creating any negative indices.
+ //
+ // Since the spec notes that the chunk can also reference args using
+ // '...', we also leave them on the stack.
+
+ // stack: debug.traceback(), compiled chunk
+ // create arg table pre-sized to hold the args array, plus [0] and ['n']
+ lua_createtable(L, narrow(args.size()), 2);
+ // stack: debug.traceback(), compiled chunk, arg table
+ int argi = lua_absindex(L, -1);
+ lua_Integer i = 0;
+ // store desc (e.g. script name) as arg[0]
+ lua_pushinteger(L, i);
+ lua_pushstdstring(L, desc);
+ lua_rawset(L, argi); // rawset() pops key and value
+ // store args.size() as arg.n
+ lua_pushinteger(L, narrow(args.size()));
+ lua_setfield(L, argi, "n"); // setfield() pops value
+ for (const auto& arg : args)
+ {
+ // push each arg in order
+ lua_pushstdstring(L, arg);
+ // push index
+ lua_pushinteger(L, ++i);
+ // duplicate arg[i] to store in arg table
+ lua_pushvalue(L, -2);
+ // stack: ..., arg[i], i, arg[i]
+ lua_rawset(L, argi);
+ // leave ..., arg[i] on stack
+ }
+ // stack: debug.traceback(), compiled chunk, arg, arg[1], arg[2], ...
+ // duplicate the arg table to store it
+ lua_pushvalue(L, argi);
+ lua_setglobal(L, "arg");
+ lua_remove(L, argi);
+ // stack: debug.traceback(), compiled chunk, arg[1], arg[2], ...
// It's important to pass LUA_MULTRET as the expected number of return
// values: if we pass any fixed number, we discard any returned values
// beyond that number.
- return lua_pcall(L, 0, LUA_MULTRET, -2);
+ return lua_pcall(L, int(args.size()), LUA_MULTRET, traceback);
}
int loadstring(lua_State *L, const std::string &desc, const std::string &text)
{
+ lluau_checkstack(L, 1);
size_t bytecodeSize = 0;
// The char* returned by luau_compile() must be freed by calling free().
// Use unique_ptr so the memory will be freed even if luau_load() throws.
@@ -529,8 +587,7 @@ int lua_metaipair(lua_State* L);
} // anonymous namespace
-LuaState::LuaState(script_finished_fn cb):
- mCallback(cb)
+LuaState::LuaState()
{
/*---------------------------- feature flag ----------------------------*/
try
@@ -715,80 +772,74 @@ LuaState::~LuaState()
return;
/*---------------------------- feature flag ----------------------------*/
- if (mFeature)
+ if (! mFeature)
+ return;
/*---------------------------- feature flag ----------------------------*/
+
+ // We're just about to destroy this lua_State mState. Did this Lua chunk
+ // register any atexit() functions?
+ lluau_checkstack(mState, 3);
+ // look up Registry.atexit
+ lua_getfield(mState, LUA_REGISTRYINDEX, "atexit");
+ // stack contains Registry.atexit
+ if (lua_istable(mState, -1))
{
- // We're just about to destroy this lua_State mState. Did this Lua chunk
- // register any atexit() functions?
- lluau_checkstack(mState, 3);
- // look up Registry.atexit
- lua_getfield(mState, LUA_REGISTRYINDEX, "atexit");
- // stack contains Registry.atexit
- if (lua_istable(mState, -1))
+ // We happen to know that Registry.atexit is built by appending array
+ // entries using table.insert(). That's important because it means
+ // there are no holes, and therefore lua_objlen() should be correct.
+ // That's important because we walk the atexit table backwards, to
+ // destroy last the things we created (passed to LL.atexit()) first.
+ int len(lua_objlen(mState, -1));
+ LL_DEBUGS("Lua") << LLCoros::getName() << ": Registry.atexit is a table with "
+ << len << " entries" << LL_ENDL;
+
+ // Push debug.traceback() onto the stack as lua_pcall()'s error
+ // handler function. On error, lua_pcall() calls the specified error
+ // handler function with the original error message; the message
+ // returned by the error handler is then returned by lua_pcall().
+ // Luau's debug.traceback() is called with a message to prepend to the
+ // returned traceback string. Almost as if they'd been designed to
+ // work together...
+ lua_getglobal(mState, "debug");
+ lua_getfield(mState, -1, "traceback");
+ // ditch "debug"
+ lua_remove(mState, -2);
+ // stack now contains atexit, debug.traceback()
+
+ for (int i(len); i >= 1; --i)
{
- // We happen to know that Registry.atexit is built by appending array
- // entries using table.insert(). That's important because it means
- // there are no holes, and therefore lua_objlen() should be correct.
- // That's important because we walk the atexit table backwards, to
- // destroy last the things we created (passed to LL.atexit()) first.
- int len(lua_objlen(mState, -1));
- LL_DEBUGS("Lua") << LLCoros::getName() << ": Registry.atexit is a table with "
- << len << " entries" << LL_ENDL;
-
- // Push debug.traceback() onto the stack as lua_pcall()'s error
- // handler function. On error, lua_pcall() calls the specified error
- // handler function with the original error message; the message
- // returned by the error handler is then returned by lua_pcall().
- // Luau's debug.traceback() is called with a message to prepend to the
- // returned traceback string. Almost as if they'd been designed to
- // work together...
- lua_getglobal(mState, "debug");
- lua_getfield(mState, -1, "traceback");
- // ditch "debug"
- lua_remove(mState, -2);
- // stack now contains atexit, debug.traceback()
-
- for (int i(len); i >= 1; --i)
+ lua_pushinteger(mState, i);
+ // stack contains Registry.atexit, debug.traceback(), i
+ lua_gettable(mState, -3);
+ // stack contains Registry.atexit, debug.traceback(), atexit[i]
+ // Call atexit[i](), no args, no return values.
+ // Use lua_pcall() because errors in any one atexit() function
+ // shouldn't cancel the rest of them. Pass debug.traceback() as
+ // the error handler function.
+ LL_DEBUGS("Lua") << LLCoros::getName()
+ << ": calling atexit(" << i << ")" << LL_ENDL;
+ if (lua_pcall(mState, 0, 0, -2) != LUA_OK)
{
- lua_pushinteger(mState, i);
- // stack contains Registry.atexit, debug.traceback(), i
- lua_gettable(mState, -3);
- // stack contains Registry.atexit, debug.traceback(), atexit[i]
- // Call atexit[i](), no args, no return values.
- // Use lua_pcall() because errors in any one atexit() function
- // shouldn't cancel the rest of them. Pass debug.traceback() as
- // the error handler function.
- LL_DEBUGS("Lua") << LLCoros::getName()
- << ": calling atexit(" << i << ")" << LL_ENDL;
- if (lua_pcall(mState, 0, 0, -2) != LUA_OK)
- {
- auto error{ lua_tostdstring(mState, -1) };
- LL_WARNS("Lua") << LLCoros::getName()
- << ": atexit(" << i << ") error: " << error << LL_ENDL;
- // pop error message
- lua_pop(mState, 1);
- }
- LL_DEBUGS("Lua") << LLCoros::getName() << ": atexit(" << i << ") done" << LL_ENDL;
- // lua_pcall() has already popped atexit[i]:
- // stack contains atexit, debug.traceback()
+ auto error{ lua_tostdstring(mState, -1) };
+ LL_WARNS("Lua") << LLCoros::getName()
+ << ": atexit(" << i << ") error: " << error << LL_ENDL;
+ // pop error message
+ lua_pop(mState, 1);
}
- // pop debug.traceback()
- lua_pop(mState, 1);
+ LL_DEBUGS("Lua") << LLCoros::getName() << ": atexit(" << i << ") done" << LL_ENDL;
+ // lua_pcall() has already popped atexit[i]:
+ // stack contains atexit, debug.traceback()
}
- // pop Registry.atexit (either table or nil)
+ // pop debug.traceback()
lua_pop(mState, 1);
-
- // with the demise of this LuaState, remove sLuaStateMap entry
- sLuaStateMap.erase(mState);
-
- lua_close(mState);
}
+ // pop Registry.atexit (either table or nil)
+ lua_pop(mState, 1);
- if (mCallback)
- {
- // mError potentially set by previous checkLua() call(s)
- mCallback(mError);
- }
+ // with the demise of this LuaState, remove sLuaStateMap entry
+ sLuaStateMap.erase(mState);
+
+ lua_close(mState);
}
bool LuaState::checkLua(const std::string& desc, int r)
@@ -804,7 +855,8 @@ bool LuaState::checkLua(const std::string& desc, int r)
return true;
}
-std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string& text)
+std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string& text,
+ const std::vector<std::string>& args)
{
/*---------------------------- feature flag ----------------------------*/
if (! mFeature)
@@ -827,7 +879,7 @@ std::pair<int, LLSD> LuaState::expr(const std::string& desc, const std::string&
};
LL_INFOS("Lua") << desc << " run" << LL_ENDL;
- if (! checkLua(desc, lluau::dostring(mState, desc, text)))
+ if (! checkLua(desc, lluau::dostring(mState, desc, text, args)))
{
LL_WARNS("Lua") << desc << " error: " << mError << LL_ENDL;
return { -1, mError };
@@ -952,8 +1004,8 @@ void LuaState::check_interrupts_counter()
}
else if (mInterrupts % INTERRUPTS_SUSPEND_LIMIT == 0)
{
- LL_DEBUGS("Lua") << LLCoros::getName() << " suspending at " << mInterrupts
- << " interrupts" << LL_ENDL;
+ LL_DEBUGS("Lua.suspend") << LLCoros::getName() << " suspending at "
+ << mInterrupts << " interrupts" << LL_ENDL;
llcoro::suspend();
}
}
diff --git a/indra/llcommon/lua_function.h b/indra/llcommon/lua_function.h
index 10c201c234..ae6e0bf7ba 100644
--- a/indra/llcommon/lua_function.h
+++ b/indra/llcommon/lua_function.h
@@ -19,6 +19,7 @@
#include "fsyspath.h"
#include "llerror.h"
#include "llsd.h"
+#include "scriptcommand.h"
#include "stringize.h"
#include <exception> // std::uncaught_exceptions()
#include <memory> // std::shared_ptr
@@ -26,6 +27,7 @@
#include <typeinfo>
#include <unordered_map>
#include <utility> // std::pair
+#include <vector>
class LuaListener;
@@ -55,8 +57,10 @@ namespace lluau
// luau removed lua_dostring(), but since we perform the equivalent luau
// sequence in multiple places, encapsulate it. desc and text are strings
// rather than string_views because dostring() needs pointers to nul-
- // terminated char arrays.
- int dostring(lua_State* L, const std::string& desc, const std::string& text);
+ // terminated char arrays. Any args are pushed to the Lua stack before
+ // calling the Lua chunk in text.
+ int dostring(lua_State* L, const std::string& desc, const std::string& text,
+ const std::vector<std::string>& args={});
int loadstring(lua_State* L, const std::string& desc, const std::string& text);
fsyspath source_path(lua_State* L);
@@ -79,9 +83,7 @@ void lua_pushllsd(lua_State* L, const LLSD& data);
class LuaState
{
public:
- typedef std::function<void(std::string msg)> script_finished_fn;
-
- LuaState(script_finished_fn cb={});
+ LuaState();
LuaState(const LuaState&) = delete;
LuaState& operator=(const LuaState&) = delete;
@@ -92,13 +94,19 @@ public:
// expr() is for when we want to capture any results left on the stack
// by a Lua expression, possibly including multiple return values.
+ // Pass:
+ // desc = description used for logging et al.
+ // text = Lua chunk to execute, e.g. contents of a script file
+ // args = arguments, if any, to pass to script file
+ // Returns:
// int < 0 means error, and LLSD::asString() is the error message.
// int == 0 with LLSD::isUndefined() means the Lua expression returned no
// results.
// int == 1 means the Lua expression returned one result.
// int > 1 with LLSD::isArray() means the Lua expression returned
// multiple results, represented as the entries of the array.
- std::pair<int, LLSD> expr(const std::string& desc, const std::string& text);
+ std::pair<int, LLSD> expr(const std::string& desc, const std::string& text,
+ const std::vector<std::string>& args={});
operator lua_State*() const { return mState; }
@@ -118,7 +126,6 @@ private:
/*---------------------------- feature flag ----------------------------*/
bool mFeature{ false };
/*---------------------------- feature flag ----------------------------*/
- script_finished_fn mCallback;
lua_State* mState{ nullptr };
std::string mError;
S32 mInterrupts{ 0 };
diff --git a/indra/llcommon/scriptcommand.cpp b/indra/llcommon/scriptcommand.cpp
new file mode 100644
index 0000000000..79afbc2063
--- /dev/null
+++ b/indra/llcommon/scriptcommand.cpp
@@ -0,0 +1,117 @@
+/**
+ * @file scriptcommand.cpp
+ * @author Nat Goodspeed
+ * @date 2024-09-16
+ * @brief Implementation for scriptcommand.
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+// Precompiled header
+#include "linden_common.h"
+// associated header
+#include "scriptcommand.h"
+// STL headers
+// std headers
+#include <sstream>
+// external library headers
+// other Linden headers
+#include "fsyspath.h"
+#include "llerror.h"
+#include "llsdutil.h"
+#include "llstring.h"
+#include "stringize.h"
+
+ScriptCommand::ScriptCommand(const std::string& command, const LLSD& path,
+ const std::string& base)
+{
+ fsyspath basepath(base);
+ // Use LLStringUtil::getTokens() to parse the script command line
+ args = LLStringUtil::getTokens(command,
+ " \t\r\n", // drop_delims
+ "", // no keep_delims
+ "\"'", // either kind of quotes
+ "\\"); // backslash escape
+ // search for args[0] on paths
+ if (search(args[0], path, basepath))
+ {
+ // The first token is in fact the script filename. Now that we've
+ // found the script file, we've consumed that token. The rest are
+ // command-line arguments.
+ args.erase(args.begin());
+ return;
+ }
+
+ // Parsing the command line produced a script file path we can't find.
+ // Maybe that's because there are spaces in the original pathname that
+ // were neither quoted nor escaped? See if we can find the whole original
+ // command line string.
+ if (search(command, path, basepath))
+ {
+ // Here we found script, using the whole input command line as its
+ // pathname. Discard any parts of it we mistook for command-line
+ // arguments.
+ args.clear();
+ return;
+ }
+
+ // Couldn't find the script either way. Is it because we can't even check
+ // existence?
+ if (! mError.empty())
+ {
+ return;
+ }
+
+ // No, existence check works, we just can't find the script.
+ std::ostringstream msgstream;
+ msgstream << "Can't find script file " << std::quoted(args[0]);
+ if (command != args[0])
+ {
+ msgstream << " or " << std::quoted(command);
+ }
+ if (path.size() > 0)
+ {
+ msgstream << " on " << path;
+ }
+ if (! base.empty())
+ {
+ msgstream << " relative to " << base;
+ }
+ mError = msgstream.str();
+ LL_WARNS("Lua") << mError << LL_ENDL;
+}
+
+bool ScriptCommand::search(const fsyspath& script, const LLSD& paths, const fsyspath& base)
+{
+ for (const auto& path : llsd::inArray(paths))
+ {
+ // If a path is already absolute, (otherpath / path) preserves it.
+ // Explicitly instantiate fsyspath for every string conversion to
+ // properly convert UTF-8 filename strings on Windows.
+ fsyspath absscript{ base / fsyspath(path.asString()) / script };
+ bool exists;
+ try
+ {
+ exists = std::filesystem::exists(absscript);
+ }
+ catch (const std::filesystem::filesystem_error& exc)
+ {
+ mError = stringize("Can't check existence: ", exc.what());
+ LL_WARNS("Lua") << mError << LL_ENDL;
+ return false;
+ }
+ if (exists)
+ {
+ this->script = absscript.string();
+ return true;
+ }
+ }
+ return false;
+}
+
+std::string ScriptCommand::error() const
+{
+ return mError;
+}
diff --git a/indra/llcommon/scriptcommand.h b/indra/llcommon/scriptcommand.h
new file mode 100644
index 0000000000..dafed552bb
--- /dev/null
+++ b/indra/llcommon/scriptcommand.h
@@ -0,0 +1,64 @@
+/**
+ * @file scriptcommand.h
+ * @author Nat Goodspeed
+ * @date 2024-09-16
+ * @brief ScriptCommand parses a string into a script filepath plus arguments.
+ *
+ * $LicenseInfo:firstyear=2024&license=viewerlgpl$
+ * Copyright (c) 2024, Linden Research, Inc.
+ * $/LicenseInfo$
+ */
+
+#if ! defined(LL_SCRIPTCOMMAND_H)
+#define LL_SCRIPTCOMMAND_H
+
+#include "llsd.h"
+
+#include <string>
+#include <vector>
+
+class fsyspath;
+
+class ScriptCommand
+{
+public:
+ /**
+ * ScriptCommand accepts a command-line string to invoke an existing
+ * script, which may or may not include arguments to pass to that script.
+ * The constructor parses the command line into tokens using quoting and
+ * escaping rules similar to bash. The first token is assumed to be the
+ * script name; ScriptCommand tries to find that script on the filesystem,
+ * trying each directory listed in the LLSD array of strings 'path'. When
+ * it finds the script file, it stores its full pathname in 'script' and
+ * the remaining tokens in 'args'.
+ *
+ * But if the constructor can't find the first token on 'path', it
+ * considers the possibility that the whole command-line string is
+ * actually a single pathname containing unescaped spaces. If that script
+ * file is found on 'path', it stores its full pathname in 'script' and
+ * leaves 'args' empty.
+ *
+ * We accept 'path' as an LLSD array rather than (e.g.)
+ * std::vector<std::string> because the primary use case involves
+ * retrieving 'path' from settings.
+ *
+ * If you also pass 'base', any directory on 'path' may be specified
+ * relative to 'base'. Otherwise, every directory on 'path' must be
+ * absolute.
+ */
+ ScriptCommand(const std::string& command, const LLSD& path=LLSD::emptyArray(),
+ const std::string& base={});
+
+ std::string script;
+ std::vector<std::string> args;
+
+ // returns empty string if no error
+ std::string error() const;
+
+private:
+ bool search(const fsyspath& script, const LLSD& path, const fsyspath& base);
+
+ std::string mError;
+};
+
+#endif /* ! defined(LL_SCRIPTCOMMAND_H) */
diff --git a/indra/llcommon/tests/llleap_test.cpp b/indra/llcommon/tests/llleap_test.cpp
index 45395e5fce..773b373e72 100644
--- a/indra/llcommon/tests/llleap_test.cpp
+++ b/indra/llcommon/tests/llleap_test.cpp
@@ -300,8 +300,8 @@ namespace tut
std::string threw = catch_what<LLLeap::Error>([&BADPYTHON](){
LLLeap::create("bad exe", BADPYTHON);
});
- ensure_contains("LLLeap::create() didn't throw", threw, "failed");
- log.messageWith("failed");
+ ensure_contains("LLLeap::create() didn't throw", threw, "Can't find");
+ log.messageWith("Can't find");
log.messageWith(BADPYTHON);
// try the suppress-exception variant
ensure("bad launch returned non-NULL", ! LLLeap::create("bad exe", BADPYTHON, false));
diff --git a/indra/newview/llappviewer.cpp b/indra/newview/llappviewer.cpp
index b4fe27f57a..295e22777e 100644
--- a/indra/newview/llappviewer.cpp
+++ b/indra/newview/llappviewer.cpp
@@ -1232,23 +1232,8 @@ bool LLAppViewer::init()
"--luafile", "LuaScript",
[](const LLSD& script)
{
- LLSD paths(gSavedSettings.getLLSD("LuaCommandPath"));
- LL_DEBUGS("Lua") << "LuaCommandPath = " << paths << LL_ENDL;
- for (const auto& path : llsd::inArray(paths))
- {
- // if script path is already absolute, operator/() preserves it
- auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / path.asString());
- auto absscript{ (abspath / script.asString()) };
- std::error_code ec;
- if (std::filesystem::exists(absscript, ec))
- {
- // no completion callback: we don't need to know
- LLLUAmanager::runScriptFile(absscript.u8string());
- return; // from lambda
- }
- }
- LL_WARNS("Lua") << "--luafile " << std::quoted(script.asString())
- << " not found on " << paths << LL_ENDL;
+ // no completion callback: we don't need to know
+ LLLUAmanager::runScriptFile(script);
});
processComposeSwitch(
"LuaAutorunPath", "LuaAutorunPath",
@@ -1257,15 +1242,16 @@ bool LLAppViewer::init()
// each directory can be relative to the viewer's install
// directory -- if directory is already absolute, operator/()
// preserves it
- auto abspath(fsyspath(gDirUtilp->getAppRODataDir()) / directory.asString());
- std::string absdir(abspath.string());
+ fsyspath abspath(fsyspath(gDirUtilp->getAppRODataDir()) /
+ fsyspath(directory.asString()));
+ std::string absdir(fsyspath(abspath).string());
LL_DEBUGS("InitInfo") << "LuaAutorunPath: " << absdir << LL_ENDL;
LLDirIterator scripts(absdir, "*.lua");
std::string script;
while (scripts.next(script))
{
LL_DEBUGS("InitInfo") << "LuaAutorunPath: " << absdir << ": " << script << LL_ENDL;
- LLLUAmanager::runScriptFile((abspath / script).string(), true);
+ LLLUAmanager::runScriptFile(fsyspath(abspath / fsyspath(script)).string(), true);
}
});
diff --git a/indra/newview/llluamanager.cpp b/indra/newview/llluamanager.cpp
index 0bd21516bc..7fe5c1ece0 100644
--- a/indra/newview/llluamanager.cpp
+++ b/indra/newview/llluamanager.cpp
@@ -186,41 +186,44 @@ LLLUAmanager::script_result LLLUAmanager::waitScriptFile(const std::string& file
}
void LLLUAmanager::runScriptFile(const std::string &filename, bool autorun,
- script_result_fn result_cb, script_finished_fn finished_cb)
+ script_result_fn result_cb)
{
// A script_result_fn will be called when LuaState::expr() completes.
- LLCoros::instance().launch(filename, [filename, autorun, result_cb, finished_cb]()
+ LLCoros::instance().launch(filename, [filename, autorun, result_cb]()
{
ScriptObserver observer(LLCoros::getName(), filename);
- llifstream in_file;
- in_file.open(filename.c_str());
-
- if (in_file.is_open())
+ LLSD paths(gSavedSettings.getLLSD("LuaCommandPath"));
+ LL_DEBUGS("Lua") << "LuaCommandPath = " << paths << LL_ENDL;
+ // allow LuaCommandPath to be specified relative to install dir
+ ScriptCommand command(filename, paths, gDirUtilp->getAppRODataDir());
+ auto error = command.error();
+ if (! error.empty())
{
- if (autorun)
- {
- sAutorunScriptCount++;
- }
- sScriptCount++;
-
- // A script_finished_fn is used to initialize the LuaState.
- // It will be called when the LuaState is destroyed.
- LuaState L(finished_cb);
- std::string text{std::istreambuf_iterator<char>(in_file), {}};
- auto [count, result] = L.expr(filename, text);
if (result_cb)
{
- result_cb(count, result);
+ result_cb(-1, error);
}
+ return;
+ }
+
+ llifstream in_file;
+ in_file.open(command.script);
+ // At this point, since ScriptCommand did not report an error, we
+ // should be able to assume that 'script' exists. If we can't open it,
+ // something else is wrong?!
+ llassert(in_file.is_open());
+ if (autorun)
+ {
+ sAutorunScriptCount++;
}
- else
+ sScriptCount++;
+
+ LuaState L;
+ std::string text{std::istreambuf_iterator<char>(in_file), {}};
+ auto [count, result] = L.expr(command.script, text, command.args);
+ if (result_cb)
{
- auto msg{ stringize("unable to open script file '", filename, "'") };
- LL_WARNS("Lua") << msg << LL_ENDL;
- if (result_cb)
- {
- result_cb(-1, msg);
- }
+ result_cb(count, result);
}
});
}
@@ -242,8 +245,7 @@ LLLUAmanager::script_result LLLUAmanager::waitScriptLine(const std::string& chun
return startScriptLine(chunk).get();
}
-void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn result_cb,
- script_finished_fn finished_cb)
+void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn result_cb)
{
// find a suitable abbreviation for the chunk string
std::string shortchunk{ chunk };
@@ -255,11 +257,9 @@ void LLLUAmanager::runScriptLine(const std::string& chunk, script_result_fn resu
shortchunk = stringize(shortchunk.substr(0, shortlen), "...");
std::string desc{ "lua: " + shortchunk };
- LLCoros::instance().launch(desc, [desc, chunk, result_cb, finished_cb]()
+ LLCoros::instance().launch(desc, [desc, chunk, result_cb]()
{
- // A script_finished_fn is used to initialize the LuaState.
- // It will be called when the LuaState is destroyed.
- LuaState L(finished_cb);
+ LuaState L;
auto [count, result] = L.expr(desc, chunk);
if (result_cb)
{
diff --git a/indra/newview/llluamanager.h b/indra/newview/llluamanager.h
index df76ddd3e2..b98b5d4ef6 100644
--- a/indra/newview/llluamanager.h
+++ b/indra/newview/llluamanager.h
@@ -44,9 +44,6 @@ class LLLUAmanager
friend class ScriptObserver;
public:
- // Pass a callback with this signature to obtain the error message, if
- // any, from running a script or source string. Empty msg means success.
- typedef std::function<void(std::string msg)> script_finished_fn;
// Pass a callback with this signature to obtain the result, if any, of
// running a script or source string.
// count < 0 means error, and result.asString() is the error message.
@@ -58,20 +55,31 @@ public:
// same semantics as script_result_fn parameters
typedef std::pair<int, LLSD> script_result;
- static void runScriptFile(const std::string &filename, bool autorun = false, script_result_fn result_cb = {},
- script_finished_fn finished_cb = {});
+ // Run the script specified by the command line passed as @a filename.
+ // This can be followed by some number of command-line arguments, which
+ // a Lua script can view using either '...' or predefined global 'arg'.
+ // The script pathname or its arguments can be quoted using 'single
+ // quotes' or "double quotes", or special characters can be \escaped.
+ // runScriptFile() recognizes the case in which the whole 'filename'
+ // string is a path containing spaces; if so no arguments are permitted.
+ // In either form, if the script pathname isn't absolute, it is sought on
+ // LuaCommandPath.
+ // If autorun is true, statistics will count this as an autorun script.
+ static void runScriptFile(const std::string &filename, bool autorun = false,
+ script_result_fn result_cb = {});
// Start running a Lua script file, returning an LLCoros::Future whose
// get() method will pause the calling coroutine until it can deliver the
// (count, result) pair described above. Between startScriptFile() and
// Future::get(), the caller and the Lua script coroutine will run
// concurrently.
+ // @a filename is as described for runScriptFile().
static LLCoros::Future<script_result> startScriptFile(const std::string& filename);
// Run a Lua script file, and pause the calling coroutine until it completes.
// The return value is the (count, result) pair described above.
+ // @a filename is as described for runScriptFile().
static script_result waitScriptFile(const std::string& filename);
- static void runScriptLine(const std::string &chunk, script_result_fn result_cb = {},
- script_finished_fn finished_cb = {});
+ static void runScriptLine(const std::string &chunk, script_result_fn result_cb = {});
// Start running a Lua chunk, returning an LLCoros::Future whose
// get() method will pause the calling coroutine until it can deliver the
// (count, result) pair described above. Between startScriptLine() and
diff --git a/indra/newview/scripts/lua/frame_profile_quit.lua b/indra/newview/scripts/lua/frame_profile_quit.lua
index e3177a3f67..ad136c86d2 100644
--- a/indra/newview/scripts/lua/frame_profile_quit.lua
+++ b/indra/newview/scripts/lua/frame_profile_quit.lua
@@ -1,5 +1,11 @@
-- Trigger Develop -> Render Tests -> Frame Profile and quit
+assert(arg.n == 3, 'Usage: frame_profile_quit.lua x y z (for camera focus)')
+-- Try coercing string arguments to numbers, using Lua's implicit conversion.
+-- If the args passed as x, y, z won't convert nicely to numbers, better find
+-- out now.
+focus = {arg[1]+0, arg[2]+0, arg[3]+0}
+
LLAgent = require 'LLAgent'
logout = require 'logout'
startup = require 'startup'
@@ -8,11 +14,14 @@ UI = require 'UI'
startup.wait('STATE_STARTED')
--- Assume we logged into http://maps.secondlife.com/secondlife/Bug%20Island/220/224/27
--- (see frame_profile bash script)
+-- Figure out where we are
+camera = LLAgent.getRegionPosition()
+-- assume z is the agent's feet, add 2 meters
+camera[2] += 2
+
Timer(10, 'wait')
-LLAgent.setCamera{camera_pos={220, 224, 26}, camera_locked=true,
- focus_pos ={228, 232, 26}, focus_locked=true}
+LLAgent.setCamera{camera_pos=camera, camera_locked=true,
+ focus_pos=focus, focus_locked=true}
Timer(1, 'wait')
-- This freezes the viewer for perceptible realtime
UI.popup:tip('starting Render Tests -> Frame Profile')
diff --git a/indra/newview/scripts/lua/require/login.lua b/indra/newview/scripts/lua/require/login.lua
index 919434f3a5..37c9093a21 100644
--- a/indra/newview/scripts/lua/require/login.lua
+++ b/indra/newview/scripts/lua/require/login.lua
@@ -18,16 +18,25 @@ local function ensure_login_state(op)
end
end
+local function fullgrid(grid)
+ if string.find(grid, '.', 1, true) then
+ return grid
+ else
+ return `util.{grid}.secondlife.com`
+ end
+end
+
function login.login(...)
ensure_login_state('login')
local args = mapargs('username,grid,slurl', ...)
args.op = 'login'
+ args.grid = fullgrid(args.grid)
return leap.request('LLPanelLogin', args)
end
function login.savedLogins(grid)
ensure_login_state('savedLogins')
- return leap.request('LLPanelLogin', {op='savedLogins', grid=grid})['logins']
+ return leap.request('LLPanelLogin', {op='savedLogins', grid=fullgrid(grid)})['logins']
end
return login