Index: lldb/include/lldb/Interpreter/CommandInterpreter.h =================================================================== --- lldb/include/lldb/Interpreter/CommandInterpreter.h +++ lldb/include/lldb/Interpreter/CommandInterpreter.h @@ -551,6 +551,8 @@ bool SaveTranscript(CommandReturnObject &result, llvm::Optional output_file = llvm::None); + FileSpec GetCurrentSourceDir(); + protected: friend class Debugger; @@ -637,7 +639,13 @@ ChildrenTruncatedWarningStatus m_truncation_warning; // Whether we truncated // children and whether // the user has been told + + // FIXME: Stop using this to control adding to the history and then replace + // this with m_command_source_dirs.size(). uint32_t m_command_source_depth; + /// A stack of directory paths. When not empty, the last one is the directory + /// of the file that's currently sourced. + std::vector m_command_source_dirs; std::vector m_command_source_flags; CommandInterpreterRunResult m_result; Index: lldb/include/lldb/Interpreter/ScriptInterpreter.h =================================================================== --- lldb/include/lldb/Interpreter/ScriptInterpreter.h +++ lldb/include/lldb/Interpreter/ScriptInterpreter.h @@ -507,7 +507,8 @@ virtual bool LoadScriptingModule(const char *filename, bool init_session, lldb_private::Status &error, - StructuredData::ObjectSP *module_sp = nullptr); + StructuredData::ObjectSP *module_sp = nullptr, + FileSpec extra_search_dir = {}); virtual bool IsReservedWord(const char *word) { return false; } Index: lldb/lldb/test/Shell/ScriptInterpreter/Python/Inputs/hello.in =================================================================== --- /dev/null +++ lldb/lldb/test/Shell/ScriptInterpreter/Python/Inputs/hello.in @@ -0,0 +1 @@ +command script import hello Index: lldb/lldb/test/Shell/ScriptInterpreter/Python/Inputs/hello.py =================================================================== --- /dev/null +++ lldb/lldb/test/Shell/ScriptInterpreter/Python/Inputs/hello.py @@ -0,0 +1,7 @@ +import lldb + +def hello(debugger, command, result, internal_dict): + print("Hello, World!") + +def __lldb_init_module(debugger, internal_dict): + debugger.HandleCommand('command script add -f hello.hello hello') Index: lldb/lldb/test/Shell/ScriptInterpreter/Python/Inputs/zip.in =================================================================== --- /dev/null +++ lldb/lldb/test/Shell/ScriptInterpreter/Python/Inputs/zip.in @@ -0,0 +1 @@ +command script import zip Index: lldb/lldb/test/Shell/ScriptInterpreter/Python/Inputs/zip.py =================================================================== --- /dev/null +++ lldb/lldb/test/Shell/ScriptInterpreter/Python/Inputs/zip.py @@ -0,0 +1,7 @@ +import lldb + +def zip(debugger, command, result, internal_dict): + print("95126") + +def __lldb_init_module(debugger, internal_dict): + debugger.HandleCommand('command script add -f zip.zip zip') Index: lldb/lldb/test/Shell/ScriptInterpreter/Python/relative_import.test =================================================================== --- /dev/null +++ lldb/lldb/test/Shell/ScriptInterpreter/Python/relative_import.test @@ -0,0 +1,11 @@ +# REQUIRES: python + +# RUN: rm -rf %t && mkdir -p %t/foo/bar +# RUN: cp %S/Inputs/zip.py %t/foo +# RUN: cp %S/Inputs/zip.in %t/foo +# RUN: cp %S/Inputs/hello.py %t/foo/bar +# RUN: cp %S/Inputs/hello.in %t/foo/bar +# RUN: echo 'command source %t/foo/bar/hello.in' >> %t/foo/zip.in +# RUN: %lldb --script-language python -o 'command source %t/foo/zip.in' -o 'zip' -o 'hello' 2>&1 | FileCheck %s +# CHECK: 95126 +# CHECK: Hello, World! Index: lldb/source/Commands/CommandObjectCommands.cpp =================================================================== --- lldb/source/Commands/CommandObjectCommands.cpp +++ lldb/source/Commands/CommandObjectCommands.cpp @@ -1294,6 +1294,9 @@ return false; } + FileSpec source_dir = + GetDebugger().GetCommandInterpreter().GetCurrentSourceDir(); + for (auto &entry : command.entries()) { Status error; @@ -1308,7 +1311,7 @@ // more) m_exe_ctx.Clear(); if (GetDebugger().GetScriptInterpreter()->LoadScriptingModule( - entry.c_str(), init_session, error)) { + entry.c_str(), init_session, error, nullptr, source_dir)) { result.SetStatus(eReturnStatusSuccessFinishNoResult); } else { result.AppendErrorWithFormat("module importing failed: %s", Index: lldb/source/Interpreter/CommandInterpreter.cpp =================================================================== --- lldb/source/Interpreter/CommandInterpreter.cpp +++ lldb/source/Interpreter/CommandInterpreter.cpp @@ -2554,11 +2554,15 @@ debugger.SetAsyncExecution(false); m_command_source_depth++; + m_command_source_dirs.push_back(cmd_file.CopyByRemovingLastPathComponent()); debugger.RunIOHandlerSync(io_handler_sp); if (!m_command_source_flags.empty()) m_command_source_flags.pop_back(); + + m_command_source_dirs.pop_back(); m_command_source_depth--; + result.SetStatus(eReturnStatusSuccessFinishNoResult); debugger.SetAsyncExecution(old_async_execution); } @@ -2964,6 +2968,12 @@ return true; } +FileSpec CommandInterpreter::GetCurrentSourceDir() { + if (m_command_source_dirs.empty()) + return {}; + return m_command_source_dirs.back(); +} + void CommandInterpreter::GetLLDBCommandsFromIOHandler( const char *prompt, IOHandlerDelegate &delegate, void *baton) { Debugger &debugger = GetDebugger(); Index: lldb/source/Interpreter/ScriptInterpreter.cpp =================================================================== --- lldb/source/Interpreter/ScriptInterpreter.cpp +++ lldb/source/Interpreter/ScriptInterpreter.cpp @@ -47,9 +47,11 @@ "This script interpreter does not support watchpoint callbacks."); } -bool ScriptInterpreter::LoadScriptingModule( - const char *filename, bool init_session, lldb_private::Status &error, - StructuredData::ObjectSP *module_sp) { +bool ScriptInterpreter::LoadScriptingModule(const char *filename, + bool init_session, + lldb_private::Status &error, + StructuredData::ObjectSP *module_sp, + FileSpec extra_search_dir) { error.SetErrorString( "This script interpreter does not support importing modules."); return false; Index: lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h +++ lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h @@ -25,10 +25,10 @@ void ExecuteInterpreterLoop() override; - bool - LoadScriptingModule(const char *filename, bool init_session, - lldb_private::Status &error, - StructuredData::ObjectSP *module_sp = nullptr) override; + bool LoadScriptingModule(const char *filename, bool init_session, + lldb_private::Status &error, + StructuredData::ObjectSP *module_sp = nullptr, + FileSpec extra_search_dir = {}) override; // Static Functions static void Initialize(); Index: lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp +++ lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp @@ -124,7 +124,7 @@ bool ScriptInterpreterLua::LoadScriptingModule( const char *filename, bool init_session, lldb_private::Status &error, - StructuredData::ObjectSP *module_sp) { + StructuredData::ObjectSP *module_sp, FileSpec extra_search_dir) { FileSystem::Instance().Collect(filename); if (llvm::Error e = m_lua->LoadModule(filename)) { Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp @@ -2733,8 +2733,9 @@ bool ScriptInterpreterPythonImpl::LoadScriptingModule( const char *pathname, bool init_session, lldb_private::Status &error, - StructuredData::ObjectSP *module_sp) { + StructuredData::ObjectSP *module_sp, FileSpec extra_search_dir) { namespace fs = llvm::sys::fs; + namespace path = llvm::sys::path; if (!pathname || !pathname[0]) { error.SetErrorString("invalid pathname"); @@ -2748,7 +2749,8 @@ FileSystem::Instance().Collect(target_file); std::string basename(target_file.GetFilename().GetCString()); - StreamString command_stream; + fs::file_status st; + std::error_code ec = status(target_file.GetPath(), st); // Before executing Python code, lock the GIL. Locker py_lock(this, @@ -2756,31 +2758,17 @@ (init_session ? Locker::InitSession : 0) | Locker::NoSTDIN, Locker::FreeAcquiredLock | (init_session ? Locker::TearDownSession : 0)); - fs::file_status st; - std::error_code ec = status(target_file.GetPath(), st); - if (ec || st.type() == fs::file_type::status_error || - st.type() == fs::file_type::type_unknown || - st.type() == fs::file_type::file_not_found) { - // if not a valid file of any sort, check if it might be a filename still - // dot can't be used but / and \ can, and if either is found, reject - if (strchr(pathname, '\\') || strchr(pathname, '/')) { - error.SetErrorString("invalid pathname"); - return false; - } - basename = pathname; // not a filename, probably a package of some sort, - // let it go through - } else if (is_directory(st) || is_regular_file(st)) { - if (target_file.GetDirectory().IsEmpty()) { - error.SetErrorString("invalid directory name"); - return false; + auto ExtendSysPath = [this](std::string directory) -> llvm::Error { + if (directory.empty()) { + return llvm::make_error( + "invalid directory name", llvm::inconvertibleErrorCode()); } - std::string directory = target_file.GetDirectory().GetCString(); replace_all(directory, "\\", "\\\\"); replace_all(directory, "'", "\\'"); - // now make sure that Python has "directory" in the search path + // Make sure that Python has "directory" in the search path. StreamString command_stream; command_stream.Printf("if not (sys.path.__contains__('%s')):\n " "sys.path.insert(1,'%s');\n\n", @@ -2792,13 +2780,51 @@ .SetSetLLDBGlobals(false)) .Success(); if (!syspath_retval) { - error.SetErrorString("Python sys.path handling failed"); - return false; + return llvm::make_error( + "Python sys.path handling failed", llvm::inconvertibleErrorCode()); } - } else { - error.SetErrorString("no known way to import this module specification"); - return false; + return llvm::Error::success(); + }; + + if (ec || st.type() == fs::file_type::status_error || + st.type() == fs::file_type::type_unknown || + st.type() == fs::file_type::file_not_found) { + // The path name doesn't exists. It might be a relative import in which + // case we need to resolve it using to the extra search directory if + // provided. Python doesn't allow having directories in a module name. If + // the path name contains a path separator we need to add it to the system + // path. For example, if the pathname is `bar/baz` and the search path is + // `foo`, we need to add `foo/bar` to the system path and import `baz`. + // + // If no extra search path is given and the path name contains a path + // separator, we know it's invalid and can reject is immediately. If no + // path separators are found defer, to Python and let it deal with it. + if (extra_search_dir) { + FileSpec sys_path = extra_search_dir; + ; + if (ConstString directory = target_file.GetDirectory()) { + llvm::SmallString<128> buffer; + extra_search_dir.GetPath(buffer); + path::append(buffer, directory.GetCString()); + sys_path.SetPath(buffer); + } + if (llvm::Error e = ExtendSysPath(sys_path.GetPath())) { + error = std::move(e); + return false; + } + } else if (strchr(pathname, '\\') || strchr(pathname, '/')) { + error.SetErrorString("invalid pathname"); + return false; + } else { + basename = pathname; + } + } else if (is_directory(st) || is_regular_file(st)) { + if (llvm::Error e = + ExtendSysPath(target_file.GetDirectory().GetCString())) { + error = std::move(e); + return false; + } } // Strip .py or .pyc extension @@ -2811,6 +2837,7 @@ } // check if the module is already import-ed + StreamString command_stream; command_stream.Clear(); command_stream.Printf("sys.modules.__contains__('%s')", basename.c_str()); bool does_contain = false; Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h =================================================================== --- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h +++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h @@ -231,10 +231,10 @@ bool RunScriptFormatKeyword(const char *impl_function, ValueObject *value, std::string &output, Status &error) override; - bool - LoadScriptingModule(const char *filename, bool init_session, - lldb_private::Status &error, - StructuredData::ObjectSP *module_sp = nullptr) override; + bool LoadScriptingModule(const char *filename, bool init_session, + lldb_private::Status &error, + StructuredData::ObjectSP *module_sp = nullptr, + FileSpec extra_search_dir = {}) override; bool IsReservedWord(const char *word) override; Index: lldb/test/Shell/ScriptInterpreter/Python/Inputs/hello.in =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Python/Inputs/hello.in @@ -0,0 +1 @@ +command script import baz/hello.py Index: lldb/test/Shell/ScriptInterpreter/Python/Inputs/hello.py =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Python/Inputs/hello.py @@ -0,0 +1,7 @@ +import lldb + +def hello(debugger, command, result, internal_dict): + print("Hello, World!") + +def __lldb_init_module(debugger, internal_dict): + debugger.HandleCommand('command script add -f hello.hello hello') Index: lldb/test/Shell/ScriptInterpreter/Python/Inputs/zip.in =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Python/Inputs/zip.in @@ -0,0 +1 @@ +command script import zip Index: lldb/test/Shell/ScriptInterpreter/Python/Inputs/zip.py =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Python/Inputs/zip.py @@ -0,0 +1,7 @@ +import lldb + +def zip(debugger, command, result, internal_dict): + print("95126") + +def __lldb_init_module(debugger, internal_dict): + debugger.HandleCommand('command script add -f zip.zip zip') Index: lldb/test/Shell/ScriptInterpreter/Python/relative_import.test =================================================================== --- /dev/null +++ lldb/test/Shell/ScriptInterpreter/Python/relative_import.test @@ -0,0 +1,11 @@ +# REQUIRES: python + +# RUN: rm -rf %t && mkdir -p %t/foo/bar/baz +# RUN: cp %S/Inputs/zip.py %t/foo +# RUN: cp %S/Inputs/zip.in %t/foo +# RUN: cp %S/Inputs/hello.py %t/foo/bar/baz +# RUN: cp %S/Inputs/hello.in %t/foo/bar +# RUN: echo 'command source %t/foo/bar/hello.in' >> %t/foo/zip.in +# RUN: %lldb --script-language python -o 'command source %t/foo/zip.in' -o 'zip' -o 'hello' 2>&1 | FileCheck %s +# CHECK: 95126 +# CHECK: Hello, World!