Skip to content

Commit 00bb397

Browse files
committed
[lldb] Support Python imports relative the to the current file being sourced
Make it possible to use a relative path in command script import to the location of the file being sourced. This allows the user to put Python scripts next to LLDB command files and importing them without having to specify an absolute path. To enable this behavior pass `-c` to `command script import`. The argument can only be used when sourcing the command from a file. rdar://68310384 Differential revision: https://reviews.llvm.org/D89334
1 parent d028d2b commit 00bb397

File tree

13 files changed

+182
-59
lines changed

13 files changed

+182
-59
lines changed

lldb/include/lldb/Interpreter/CommandInterpreter.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,8 @@ class CommandInterpreter : public Broadcaster,
551551
bool SaveTranscript(CommandReturnObject &result,
552552
llvm::Optional<std::string> output_file = llvm::None);
553553

554+
FileSpec GetCurrentSourceDir();
555+
554556
protected:
555557
friend class Debugger;
556558

@@ -637,7 +639,13 @@ class CommandInterpreter : public Broadcaster,
637639
ChildrenTruncatedWarningStatus m_truncation_warning; // Whether we truncated
638640
// children and whether
639641
// the user has been told
642+
643+
// FIXME: Stop using this to control adding to the history and then replace
644+
// this with m_command_source_dirs.size().
640645
uint32_t m_command_source_depth;
646+
/// A stack of directory paths. When not empty, the last one is the directory
647+
/// of the file that's currently sourced.
648+
std::vector<FileSpec> m_command_source_dirs;
641649
std::vector<uint32_t> m_command_source_flags;
642650
CommandInterpreterRunResult m_result;
643651

lldb/include/lldb/Interpreter/ScriptInterpreter.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -507,7 +507,8 @@ class ScriptInterpreter : public PluginInterface {
507507
virtual bool
508508
LoadScriptingModule(const char *filename, bool init_session,
509509
lldb_private::Status &error,
510-
StructuredData::ObjectSP *module_sp = nullptr);
510+
StructuredData::ObjectSP *module_sp = nullptr,
511+
FileSpec extra_search_dir = {});
511512

512513
virtual bool IsReservedWord(const char *word) { return false; }
513514

lldb/source/Commands/CommandObjectCommands.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1272,6 +1272,9 @@ class CommandObjectCommandsScriptImport : public CommandObjectParsed {
12721272
case 'r':
12731273
// NO-OP
12741274
break;
1275+
case 'c':
1276+
relative_to_command_file = true;
1277+
break;
12751278
default:
12761279
llvm_unreachable("Unimplemented option");
12771280
}
@@ -1280,11 +1283,13 @@ class CommandObjectCommandsScriptImport : public CommandObjectParsed {
12801283
}
12811284

12821285
void OptionParsingStarting(ExecutionContext *execution_context) override {
1286+
relative_to_command_file = false;
12831287
}
12841288

12851289
llvm::ArrayRef<OptionDefinition> GetDefinitions() override {
12861290
return llvm::makeArrayRef(g_script_import_options);
12871291
}
1292+
bool relative_to_command_file = false;
12881293
};
12891294

12901295
bool DoExecute(Args &command, CommandReturnObject &result) override {
@@ -1294,6 +1299,17 @@ class CommandObjectCommandsScriptImport : public CommandObjectParsed {
12941299
return false;
12951300
}
12961301

1302+
FileSpec source_dir = {};
1303+
if (m_options.relative_to_command_file) {
1304+
source_dir = GetDebugger().GetCommandInterpreter().GetCurrentSourceDir();
1305+
if (!source_dir) {
1306+
result.AppendError("command script import -c can only be specified "
1307+
"from a command file");
1308+
result.SetStatus(eReturnStatusFailed);
1309+
return false;
1310+
}
1311+
}
1312+
12971313
for (auto &entry : command.entries()) {
12981314
Status error;
12991315

@@ -1308,7 +1324,7 @@ class CommandObjectCommandsScriptImport : public CommandObjectParsed {
13081324
// more)
13091325
m_exe_ctx.Clear();
13101326
if (GetDebugger().GetScriptInterpreter()->LoadScriptingModule(
1311-
entry.c_str(), init_session, error)) {
1327+
entry.c_str(), init_session, error, nullptr, source_dir)) {
13121328
result.SetStatus(eReturnStatusSuccessFinishNoResult);
13131329
} else {
13141330
result.AppendErrorWithFormat("module importing failed: %s",

lldb/source/Commands/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,10 @@ let Command = "script import" in {
704704
Desc<"Allow the script to be loaded even if it was already loaded before. "
705705
"This argument exists for backwards compatibility, but reloading is always "
706706
"allowed, whether you specify it or not.">;
707+
def relative_to_command_file : Option<"relative-to-command-file", "c">,
708+
Group<1>, Desc<"Resolve non-absolute paths relative to the location of the "
709+
"current command file. This argument can only be used when the command is "
710+
"being sourced from a file.">;
707711
}
708712

709713
let Command = "script add" in {

lldb/source/Interpreter/CommandInterpreter.cpp

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2554,11 +2554,15 @@ void CommandInterpreter::HandleCommandsFromFile(
25542554
debugger.SetAsyncExecution(false);
25552555

25562556
m_command_source_depth++;
2557+
m_command_source_dirs.push_back(cmd_file.CopyByRemovingLastPathComponent());
25572558

25582559
debugger.RunIOHandlerSync(io_handler_sp);
25592560
if (!m_command_source_flags.empty())
25602561
m_command_source_flags.pop_back();
2562+
2563+
m_command_source_dirs.pop_back();
25612564
m_command_source_depth--;
2565+
25622566
result.SetStatus(eReturnStatusSuccessFinishNoResult);
25632567
debugger.SetAsyncExecution(old_async_execution);
25642568
}
@@ -2964,6 +2968,12 @@ bool CommandInterpreter::SaveTranscript(
29642968
return true;
29652969
}
29662970

2971+
FileSpec CommandInterpreter::GetCurrentSourceDir() {
2972+
if (m_command_source_dirs.empty())
2973+
return {};
2974+
return m_command_source_dirs.back();
2975+
}
2976+
29672977
void CommandInterpreter::GetLLDBCommandsFromIOHandler(
29682978
const char *prompt, IOHandlerDelegate &delegate, void *baton) {
29692979
Debugger &debugger = GetDebugger();

lldb/source/Interpreter/ScriptInterpreter.cpp

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,9 +47,11 @@ void ScriptInterpreter::CollectDataForWatchpointCommandCallback(
4747
"This script interpreter does not support watchpoint callbacks.");
4848
}
4949

50-
bool ScriptInterpreter::LoadScriptingModule(
51-
const char *filename, bool init_session, lldb_private::Status &error,
52-
StructuredData::ObjectSP *module_sp) {
50+
bool ScriptInterpreter::LoadScriptingModule(const char *filename,
51+
bool init_session,
52+
lldb_private::Status &error,
53+
StructuredData::ObjectSP *module_sp,
54+
FileSpec extra_search_dir) {
5355
error.SetErrorString(
5456
"This script interpreter does not support importing modules.");
5557
return false;

lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ void ScriptInterpreterLua::ExecuteInterpreterLoop() {
124124

125125
bool ScriptInterpreterLua::LoadScriptingModule(
126126
const char *filename, bool init_session, lldb_private::Status &error,
127-
StructuredData::ObjectSP *module_sp) {
127+
StructuredData::ObjectSP *module_sp, FileSpec extra_search_dir) {
128128

129129
FileSystem::Instance().Collect(filename);
130130
if (llvm::Error e = m_lua->LoadModule(filename)) {

lldb/source/Plugins/ScriptInterpreter/Lua/ScriptInterpreterLua.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,10 @@ class ScriptInterpreterLua : public ScriptInterpreter {
2525

2626
void ExecuteInterpreterLoop() override;
2727

28-
bool
29-
LoadScriptingModule(const char *filename, bool init_session,
30-
lldb_private::Status &error,
31-
StructuredData::ObjectSP *module_sp = nullptr) override;
28+
bool LoadScriptingModule(const char *filename, bool init_session,
29+
lldb_private::Status &error,
30+
StructuredData::ObjectSP *module_sp = nullptr,
31+
FileSpec extra_search_dir = {}) override;
3232

3333
// Static Functions
3434
static void Initialize();

lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp

Lines changed: 66 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2733,8 +2733,9 @@ uint64_t replace_all(std::string &str, const std::string &oldStr,
27332733

27342734
bool ScriptInterpreterPythonImpl::LoadScriptingModule(
27352735
const char *pathname, bool init_session, lldb_private::Status &error,
2736-
StructuredData::ObjectSP *module_sp) {
2736+
StructuredData::ObjectSP *module_sp, FileSpec extra_search_dir) {
27372737
namespace fs = llvm::sys::fs;
2738+
namespace path = llvm::sys::path;
27382739

27392740
if (!pathname || !pathname[0]) {
27402741
error.SetErrorString("invalid pathname");
@@ -2743,44 +2744,23 @@ bool ScriptInterpreterPythonImpl::LoadScriptingModule(
27432744

27442745
lldb::DebuggerSP debugger_sp = m_debugger.shared_from_this();
27452746

2746-
FileSpec target_file(pathname);
2747-
FileSystem::Instance().Resolve(target_file);
2748-
FileSystem::Instance().Collect(target_file);
2749-
std::string basename(target_file.GetFilename().GetCString());
2750-
2751-
StreamString command_stream;
2752-
27532747
// Before executing Python code, lock the GIL.
27542748
Locker py_lock(this,
27552749
Locker::AcquireLock |
27562750
(init_session ? Locker::InitSession : 0) | Locker::NoSTDIN,
27572751
Locker::FreeAcquiredLock |
27582752
(init_session ? Locker::TearDownSession : 0));
2759-
fs::file_status st;
2760-
std::error_code ec = status(target_file.GetPath(), st);
2761-
2762-
if (ec || st.type() == fs::file_type::status_error ||
2763-
st.type() == fs::file_type::type_unknown ||
2764-
st.type() == fs::file_type::file_not_found) {
2765-
// if not a valid file of any sort, check if it might be a filename still
2766-
// dot can't be used but / and \ can, and if either is found, reject
2767-
if (strchr(pathname, '\\') || strchr(pathname, '/')) {
2768-
error.SetErrorString("invalid pathname");
2769-
return false;
2770-
}
2771-
basename = pathname; // not a filename, probably a package of some sort,
2772-
// let it go through
2773-
} else if (is_directory(st) || is_regular_file(st)) {
2774-
if (target_file.GetDirectory().IsEmpty()) {
2775-
error.SetErrorString("invalid directory name");
2776-
return false;
2753+
2754+
auto ExtendSysPath = [this](std::string directory) -> llvm::Error {
2755+
if (directory.empty()) {
2756+
return llvm::make_error<llvm::StringError>(
2757+
"invalid directory name", llvm::inconvertibleErrorCode());
27772758
}
27782759

2779-
std::string directory = target_file.GetDirectory().GetCString();
27802760
replace_all(directory, "\\", "\\\\");
27812761
replace_all(directory, "'", "\\'");
27822762

2783-
// now make sure that Python has "directory" in the search path
2763+
// Make sure that Python has "directory" in the search path.
27842764
StreamString command_stream;
27852765
command_stream.Printf("if not (sys.path.__contains__('%s')):\n "
27862766
"sys.path.insert(1,'%s');\n\n",
@@ -2792,27 +2772,68 @@ bool ScriptInterpreterPythonImpl::LoadScriptingModule(
27922772
.SetSetLLDBGlobals(false))
27932773
.Success();
27942774
if (!syspath_retval) {
2795-
error.SetErrorString("Python sys.path handling failed");
2796-
return false;
2775+
return llvm::make_error<llvm::StringError>(
2776+
"Python sys.path handling failed", llvm::inconvertibleErrorCode());
27972777
}
27982778

2779+
return llvm::Error::success();
2780+
};
2781+
2782+
std::string module_name(pathname);
2783+
2784+
if (extra_search_dir) {
2785+
if (llvm::Error e = ExtendSysPath(extra_search_dir.GetPath())) {
2786+
error = std::move(e);
2787+
return false;
2788+
}
27992789
} else {
2800-
error.SetErrorString("no known way to import this module specification");
2801-
return false;
2790+
FileSpec module_file(pathname);
2791+
FileSystem::Instance().Resolve(module_file);
2792+
FileSystem::Instance().Collect(module_file);
2793+
2794+
fs::file_status st;
2795+
std::error_code ec = status(module_file.GetPath(), st);
2796+
2797+
if (ec || st.type() == fs::file_type::status_error ||
2798+
st.type() == fs::file_type::type_unknown ||
2799+
st.type() == fs::file_type::file_not_found) {
2800+
// if not a valid file of any sort, check if it might be a filename still
2801+
// dot can't be used but / and \ can, and if either is found, reject
2802+
if (strchr(pathname, '\\') || strchr(pathname, '/')) {
2803+
error.SetErrorString("invalid pathname");
2804+
return false;
2805+
}
2806+
// Not a filename, probably a package of some sort, let it go through.
2807+
} else if (is_directory(st) || is_regular_file(st)) {
2808+
if (module_file.GetDirectory().IsEmpty()) {
2809+
error.SetErrorString("invalid directory name");
2810+
return false;
2811+
}
2812+
if (llvm::Error e =
2813+
ExtendSysPath(module_file.GetDirectory().GetCString())) {
2814+
error = std::move(e);
2815+
return false;
2816+
}
2817+
module_name = module_file.GetFilename().GetCString();
2818+
} else {
2819+
error.SetErrorString("no known way to import this module specification");
2820+
return false;
2821+
}
28022822
}
28032823

28042824
// Strip .py or .pyc extension
2805-
llvm::StringRef extension = target_file.GetFileNameExtension().GetCString();
2825+
llvm::StringRef extension = llvm::sys::path::extension(module_name);
28062826
if (!extension.empty()) {
28072827
if (extension == ".py")
2808-
basename.resize(basename.length() - 3);
2828+
module_name.resize(module_name.length() - 3);
28092829
else if (extension == ".pyc")
2810-
basename.resize(basename.length() - 4);
2830+
module_name.resize(module_name.length() - 4);
28112831
}
28122832

28132833
// check if the module is already import-ed
2834+
StreamString command_stream;
28142835
command_stream.Clear();
2815-
command_stream.Printf("sys.modules.__contains__('%s')", basename.c_str());
2836+
command_stream.Printf("sys.modules.__contains__('%s')", module_name.c_str());
28162837
bool does_contain = false;
28172838
// this call will succeed if the module was ever imported in any Debugger
28182839
// in the lifetime of the process in which this LLDB framework is living
@@ -2827,9 +2848,9 @@ bool ScriptInterpreterPythonImpl::LoadScriptingModule(
28272848
// this call will fail if the module was not imported in this Debugger
28282849
// before
28292850
command_stream.Clear();
2830-
command_stream.Printf("sys.getrefcount(%s)", basename.c_str());
2851+
command_stream.Printf("sys.getrefcount(%s)", module_name.c_str());
28312852
bool was_imported_locally = GetSessionDictionary()
2832-
.GetItemForKey(PythonString(basename))
2853+
.GetItemForKey(PythonString(module_name))
28332854
.IsAllocated();
28342855

28352856
bool was_imported = (was_imported_globally || was_imported_locally);
@@ -2839,12 +2860,12 @@ bool ScriptInterpreterPythonImpl::LoadScriptingModule(
28392860

28402861
if (was_imported) {
28412862
if (!was_imported_locally)
2842-
command_stream.Printf("import %s ; reload_module(%s)", basename.c_str(),
2843-
basename.c_str());
2863+
command_stream.Printf("import %s ; reload_module(%s)",
2864+
module_name.c_str(), module_name.c_str());
28442865
else
2845-
command_stream.Printf("reload_module(%s)", basename.c_str());
2866+
command_stream.Printf("reload_module(%s)", module_name.c_str());
28462867
} else
2847-
command_stream.Printf("import %s", basename.c_str());
2868+
command_stream.Printf("import %s", module_name.c_str());
28482869

28492870
error = ExecuteMultipleLines(command_stream.GetData(),
28502871
ScriptInterpreter::ExecuteScriptOptions()
@@ -2855,16 +2876,16 @@ bool ScriptInterpreterPythonImpl::LoadScriptingModule(
28552876

28562877
// if we are here, everything worked
28572878
// call __lldb_init_module(debugger,dict)
2858-
if (!LLDBSwigPythonCallModuleInit(basename.c_str(), m_dictionary_name.c_str(),
2859-
debugger_sp)) {
2879+
if (!LLDBSwigPythonCallModuleInit(module_name.c_str(),
2880+
m_dictionary_name.c_str(), debugger_sp)) {
28602881
error.SetErrorString("calling __lldb_init_module failed");
28612882
return false;
28622883
}
28632884

28642885
if (module_sp) {
28652886
// everything went just great, now set the module object
28662887
command_stream.Clear();
2867-
command_stream.Printf("%s", basename.c_str());
2888+
command_stream.Printf("%s", module_name.c_str());
28682889
void *module_pyobj = nullptr;
28692890
if (ExecuteOneLineWithReturn(
28702891
command_stream.GetData(),

lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPythonImpl.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,10 @@ class ScriptInterpreterPythonImpl : public ScriptInterpreterPython {
231231
bool RunScriptFormatKeyword(const char *impl_function, ValueObject *value,
232232
std::string &output, Status &error) override;
233233

234-
bool
235-
LoadScriptingModule(const char *filename, bool init_session,
236-
lldb_private::Status &error,
237-
StructuredData::ObjectSP *module_sp = nullptr) override;
234+
bool LoadScriptingModule(const char *filename, bool init_session,
235+
lldb_private::Status &error,
236+
StructuredData::ObjectSP *module_sp = nullptr,
237+
FileSpec extra_search_dir = {}) override;
238238

239239
bool IsReservedWord(const char *word) override;
240240

0 commit comments

Comments
 (0)