Skip to content

Commit 18a2f6f

Browse files
committed
BUG#29897911 UNABLE TO DEFINE A COMPLEX PYTHON PLUGIN: 'IMPORT' DOES NOT WORK
This patch fixes the plugin load logic so it: - Properly adds the plugin path to the sys.path - Properly defines the plugin __file__ variable Plugin Group Support Now, a plugin folder may contain either: - A single plugin definition - Multiple plugin definitions This is determined by the presence of the initialization file, if it is present, the plugin is assumed to be a single plugin. If it is not present the plugin is assumed to be a plugin group, this is, it may contain the definition of multiple plugins. A plugin group is defined by the main plugin folder, containing sub-folders. Each subfolder will be scanned for a standard plugin definition, and loaded following the standard plugin load logic. Only the main plugin folders support multiple plugin definitions, this is, this logic is not recursive. Change-Id: I06d550bb323db07ec3c1d0c641ed9bdc143bd021
1 parent 7a3cd37 commit 18a2f6f

21 files changed

+559
-136
lines changed

modules/mod_extensible_object.cc

+26-2
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,13 @@ void Extensible_object::register_member(
341341
name + "' member.");
342342
}
343343

344+
std::string target_object;
345+
if (m_name.empty())
346+
target_object = "an unregistered extension object";
347+
else
348+
target_object =
349+
shcore::str_format("the '%s' extension object", m_name.c_str());
350+
344351
if (s_allowed_member_types.find(value.type) == s_allowed_member_types.end()) {
345352
throw shcore::Exception::argument_error("Unsupported member type.");
346353
} else if (value.type == shcore::Object) {
@@ -353,15 +360,22 @@ void Extensible_object::register_member(
353360
object->m_definition = parse_member_definition(definition);
354361
object->m_definition->name = name;
355362
register_object(object);
363+
log_debug(
364+
"The '%s' extension object has been registered as a member into %s.",
365+
name.c_str(), target_object.c_str());
356366
} else {
357367
if (value.type == shcore::Function) {
358368
auto fd = parse_function_definition(definition);
359369
fd->name = name;
360370
register_function(fd, value.as_function(), false);
371+
log_debug("The '%s' function has been registered into %s.", name.c_str(),
372+
target_object.c_str());
361373
} else {
362374
auto md = parse_member_definition(definition);
363375
md->name = name;
364376
register_property(md, value, false);
377+
log_debug("The '%s' property has been registered into %s.", name.c_str(),
378+
target_object.c_str());
365379
}
366380
}
367381
}
@@ -776,10 +790,10 @@ void Extensible_object::register_help(const std::string &brief,
776790
// Creates the help topic for the object
777791
auto type = is_global ? shcore::Topic_type::GLOBAL_OBJECT
778792
: shcore::Topic_type::OBJECT;
779-
help->add_help_topic(m_name, type, m_name, parent, mask);
793+
help->add_help_topic(m_name, type, m_qualified_name, parent, mask);
780794

781795
// Now registers the object brief, parameters and details
782-
auto prefix = shcore::str_upper(m_name);
796+
auto prefix = shcore::str_upper(m_qualified_name);
783797

784798
help->add_help(prefix, "BRIEF", brief);
785799
help->add_help(prefix, "DETAIL", &m_detail_sequence, details);
@@ -1098,4 +1112,14 @@ void Extensible_object::disable_help() {
10981112
}
10991113
}
11001114

1115+
std::string Extensible_object::get_help_id() const {
1116+
auto tokens = shcore::split_string(m_qualified_name, ".");
1117+
std::vector<std::string> id_tokens;
1118+
for (const auto &token : tokens) {
1119+
id_tokens.push_back(
1120+
shcore::get_member_name(token, shcore::current_naming_style()));
1121+
}
1122+
return shcore::str_join(id_tokens, ".");
1123+
}
1124+
11011125
} // namespace mysqlsh

modules/mod_extensible_object.h

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@ class Extensible_object
138138
std::string class_name() const override {
139139
return m_name.empty() ? "ExtensionObject" : m_name;
140140
}
141+
std::string get_help_id() const override;
141142
bool operator==(const Object_bridge &other) const override;
142143

143144
shcore::Value get_member(const std::string &prop) const override;

modules/mod_shell.cc

+6
Original file line numberDiff line numberDiff line change
@@ -1495,6 +1495,9 @@ void Shell::register_report(const std::string &name, const std::string &type,
14951495
const shcore::Function_base_ref &report,
14961496
const shcore::Dictionary_t &description) {
14971497
m_reports->register_report(name, type, report, description);
1498+
1499+
log_debug("The '%s' report of type '%s' has been registered.", name.c_str(),
1500+
type.c_str());
14981501
}
14991502

15001503
REGISTER_HELP_FUNCTION(createExtensionObject, shell);
@@ -1744,6 +1747,9 @@ void Shell::register_global(const std::string &name,
17441747

17451748
object->set_registered(name);
17461749

1750+
log_debug(
1751+
"The '%s' extension object has been registered as a global object.",
1752+
name.c_str());
17471753
} else {
17481754
throw shcore::Exception::type_error(
17491755
"Argument #2 is expected to be an extension object.");

mysqlshdk/include/scripting/common.h

+10-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2014, 2018, Oracle and/or its affiliates. All rights reserved.
2+
* Copyright (c) 2014, 2019, Oracle and/or its affiliates. All rights reserved.
33
*
44
* This program is free software; you can redistribute it and/or modify
55
* it under the terms of the GNU General Public License, version 2.0,
@@ -53,4 +53,13 @@
5353

5454
#define MYSH_FULL_VERSION MYSH_VERSION EXTRA_NAME_SUFFIX
5555

56+
namespace shcore {
57+
// A plugin file contains the path, and bool indicating whether it is
58+
// A 1st level plugin (true) or a child plugin (false)
59+
struct Plugin_definition {
60+
Plugin_definition(const std::string f, bool m) : file(f), main(m) {}
61+
std::string file;
62+
bool main;
63+
};
64+
} // namespace shcore
5665
#endif

mysqlshdk/include/scripting/jscript_context.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ class SHCORE_PUBLIC JScript_context {
8484
std::string to_string(v8::Local<v8::Value> obj);
8585

8686
std::string translate_exception(const v8::TryCatch &exc, bool interactive);
87-
bool load_plugin(const std::string &file_name);
87+
bool load_plugin(const Plugin_definition &plugin);
8888

8989
std::weak_ptr<JScript_function_storage> store(
9090
v8::Local<v8::Function> function);

mysqlshdk/include/scripting/python_context.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ class TYPES_COMMON_PUBLIC Python_context {
117117
bool is_module(const std::string &file_name);
118118
Value execute_module(const std::string &file_name,
119119
const std::vector<std::string> &argv);
120-
bool load_plugin(const std::string &file_name);
120+
bool load_plugin(const Plugin_definition &plugin);
121121

122122
Value pyobj_to_shcore_value(PyObject *value);
123123
PyObject *shcore_value_to_pyobj(const Value &value);

mysqlshdk/include/scripting/types_cpp.h

+1
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ class SHCORE_PUBLIC Cpp_object_bridge : public Object_bridge {
577577
virtual std::string &append_repr(std::string &s_out) const;
578578

579579
virtual shcore::Value help(const shcore::Argument_list &args);
580+
virtual std::string get_help_id() const { return class_name(); }
580581

581582
protected:
582583
void detect_overload_conflicts(const std::string &name,

mysqlshdk/include/shellcore/shell_core.h

+4-4
Original file line numberDiff line numberDiff line change
@@ -129,9 +129,9 @@ class SHCORE_PUBLIC Shell_language {
129129
* run-time will be available, however the global scope is not going to be
130130
* affected by the specified script.
131131
*
132-
* @param file_name - full path to the file which is going to be loaded.
132+
* @param plugin - the information of the plugin to be loaded.
133133
*/
134-
virtual bool load_plugin(const std::string &UNUSED(file_name)) {
134+
virtual bool load_plugin(const Plugin_definition &UNUSED(plugin)) {
135135
return true;
136136
}
137137

@@ -200,9 +200,9 @@ class SHCORE_PUBLIC Shell_core : public shcore::IShell_core {
200200
* Loads the specified plugin file using the current scripting language.
201201
*
202202
* @param mode - The language in which the plugin should be loaded.
203-
* @param file_name - full path to the file which is going to be loaded.
203+
* @param plugin - The information of the plugin to be loaded.
204204
*/
205-
bool load_plugin(Mode mode, const std::string &file_name);
205+
bool load_plugin(Mode mode, const Plugin_definition &plugin);
206206

207207
virtual void clear_input();
208208

mysqlshdk/include/shellcore/shell_jscript.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ class Shell_javascript : public Shell_language {
5151
void clear_input() override;
5252
std::string get_continued_input_context() override;
5353

54-
bool load_plugin(const std::string &file_name) override;
54+
bool load_plugin(const Plugin_definition &plugin) override;
5555

5656
private:
5757
void abort() noexcept;

mysqlshdk/include/shellcore/shell_python.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ class Shell_python : public Shell_language {
4949

5050
bool is_module(const std::string &file_name) override;
5151
void execute_module(const std::string &file_name) override;
52-
bool load_plugin(const std::string &file_name) override;
52+
bool load_plugin(const Plugin_definition &plugin) override;
5353

5454
std::shared_ptr<Python_context> python_context() { return _py; }
5555

mysqlshdk/scripting/jscript_context.cc

+2-1
Original file line numberDiff line numberDiff line change
@@ -1339,13 +1339,14 @@ std::string JScript_context::translate_exception(const v8::TryCatch &exc,
13391339
return format_exception(get_v8_exception_data(exc, interactive));
13401340
}
13411341

1342-
bool JScript_context::load_plugin(const std::string &file_name) {
1342+
bool JScript_context::load_plugin(const Plugin_definition &plugin) {
13431343
bool ret_val = true;
13441344
// load the file
13451345
std::string source;
13461346

13471347
shcore::Scoped_naming_style style(NamingStyle::LowerCamelCase);
13481348

1349+
std::string file_name = plugin.file;
13491350
if (load_text_file(file_name, source)) {
13501351
const auto _isolate = isolate();
13511352

mysqlshdk/scripting/python_context.cc

+106-22
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,6 @@ void Python_context::set_python_error(const shcore::Exception &exc,
631631

632632
void Python_context::set_python_error(PyObject *obj,
633633
const std::string &location) {
634-
log_info("Python error: %s", location.c_str());
635634
PyErr_SetString(obj, location.c_str());
636635
}
637636

@@ -1139,38 +1138,123 @@ Value Python_context::execute_module(const std::string &file_name,
11391138
return ret_val;
11401139
}
11411140

1142-
bool Python_context::load_plugin(const std::string &file_name) {
1143-
bool ret_val = true;
1141+
bool Python_context::load_plugin(const Plugin_definition &plugin) {
11441142
WillEnterPython lock;
1143+
bool ret_val = false;
11451144
shcore::Scoped_naming_style ns(shcore::NamingStyle::LowerCaseUnderscores);
11461145

1147-
const auto file = fopen(file_name.c_str(), "r");
1146+
PyObject *plugin_file = nullptr;
1147+
PyObject *plugin_path = nullptr;
1148+
PyObject *parent_plugin_path = nullptr;
1149+
PyObject *result = nullptr;
1150+
PyObject *globals = nullptr;
1151+
PyObject *sys_path_backup = nullptr;
1152+
FILE *file = nullptr;
1153+
bool executed = false;
1154+
std::string file_name = plugin.file;
1155+
try {
1156+
file = fopen(file_name.c_str(), "r");
1157+
if (nullptr == file) {
1158+
throw std::runtime_error(shcore::str_format(
1159+
"Failed opening the file: %s", errno_to_string(errno).c_str()));
1160+
}
1161+
1162+
plugin_file = PyString_FromString(file_name.c_str());
1163+
if (nullptr == plugin_file) {
1164+
throw std::runtime_error("Unable to get plugin file");
1165+
}
1166+
1167+
std::string plugin_dir = shcore::path::dirname(file_name);
1168+
plugin_path = PyString_FromString(plugin_dir.c_str());
1169+
if (nullptr == plugin_path) {
1170+
throw std::runtime_error("Unable to get plugin path");
1171+
}
1172+
1173+
std::string parent_plugin_dir;
1174+
if (!plugin.main) {
1175+
parent_plugin_dir = shcore::path::dirname(plugin_dir);
1176+
parent_plugin_path = PyString_FromString(parent_plugin_dir.c_str());
1177+
if (nullptr == parent_plugin_path) {
1178+
throw std::runtime_error("Unable to get the parent plugin path");
1179+
}
1180+
}
1181+
1182+
PyObject *sys_path = PySys_GetObject(const_cast<char *>("path"));
1183+
if (nullptr == sys_path) {
1184+
throw std::runtime_error("Failed getting sys.path");
1185+
}
1186+
1187+
// copy globals, so executed scripts does not pollute global
1188+
// namespace
1189+
globals = PyDict_Copy(_globals);
1190+
if (PyDict_SetItemString(globals, "__file__", plugin_file)) {
1191+
throw std::runtime_error("Failed setting __file__");
1192+
}
1193+
1194+
// Backups the original sys.path, in case the plugin load fails sys.path
1195+
// will be set back to the original content before the plugin load
1196+
sys_path_backup = PyList_GetSlice(sys_path, 0, PyList_Size(sys_path));
1197+
if (nullptr == sys_path_backup) {
1198+
std::string error = fetch_and_clear_exception();
1199+
1200+
throw std::runtime_error(
1201+
shcore::str_format("Failed backing up sys.path: %s", error.c_str()));
1202+
}
1203+
1204+
if (PyList_Insert(sys_path, 0, plugin_path)) {
1205+
throw std::runtime_error("Failed adding plugin path to sys.path");
1206+
}
1207+
1208+
if (!plugin.main) {
1209+
if (PyList_Insert(sys_path, 0, parent_plugin_path)) {
1210+
throw std::runtime_error(
1211+
"Failed adding parent plugin path to sys.path");
1212+
}
1213+
}
11481214

1149-
if (nullptr == file) {
1150-
ret_val = false;
1151-
log_error("Error loading Python file '%s':\n\tFailed opening the file: %s",
1152-
file_name.c_str(), errno_to_string(errno).c_str());
1153-
} else {
1154-
// copy globals, so executed scripts does not pollute global namespace
1155-
PyObject *globals = PyDict_Copy(_globals);
11561215
// PyRun_FileEx() will close the file
1157-
// plugins are loaded in sandboxed environment, we're not using the
1158-
// compiler flags here
1159-
const auto result =
1160-
PyRun_FileEx(file, shcore::path::basename(file_name).c_str(),
1161-
Py_file_input, globals, nullptr, true);
1216+
// plugins are loaded in sandboxed environment, we're not using
1217+
// the compiler flags here
1218+
result = PyRun_FileEx(file, shcore::path::basename(file_name).c_str(),
1219+
Py_file_input, globals, nullptr, true);
1220+
1221+
executed = true;
11621222

1223+
// In case of an error, gets the description and clears the error condition
1224+
// in python to be able to restore the sys.path
1225+
std::string error;
11631226
if (nullptr == result) {
1164-
ret_val = false;
1165-
log_error("Error loading Python file '%s':\n\tExecution failed: %s",
1166-
file_name.c_str(), fetch_and_clear_exception().c_str());
1227+
error = fetch_and_clear_exception();
11671228
}
11681229

1169-
Py_XDECREF(result);
1170-
Py_XDECREF(globals);
1230+
// The plugin paths are required on sys.path at the moment the plugin is
1231+
// loaded, after that they are not required so the original sys.path is
1232+
// restored
1233+
PySys_SetObject(const_cast<char *>("path"), sys_path_backup);
1234+
1235+
// If there was an error, raises the corresponding exception
1236+
if (nullptr == result) {
1237+
throw std::runtime_error(
1238+
shcore::str_format("Execution failed: %s", error.c_str()));
1239+
} else {
1240+
ret_val = true;
1241+
}
1242+
} catch (const std::runtime_error &error) {
1243+
log_error("Error loading Python file '%s':\n\t%s", file_name.c_str(),
1244+
error.what());
1245+
1246+
if (file && !executed) {
1247+
fclose(file);
1248+
}
11711249
}
1250+
Py_XDECREF(plugin_file);
1251+
Py_XDECREF(result);
1252+
Py_XDECREF(globals);
1253+
Py_XDECREF(plugin_path);
1254+
Py_XDECREF(sys_path_backup);
1255+
11721256
return ret_val;
1173-
}
1257+
} // namespace shcore
11741258

11751259
std::string Python_context::fetch_and_clear_exception() {
11761260
std::string exception;

mysqlshdk/scripting/types_cpp.cc

+1-1
Original file line numberDiff line numberDiff line change
@@ -617,7 +617,7 @@ shcore::Value Cpp_object_bridge::help(const shcore::Argument_list &args) {
617617
help.set_mode(mode);
618618

619619
shcore::Topic_mask mask;
620-
std::string pattern = class_name();
620+
std::string pattern = get_help_id();
621621
if (!item.empty()) {
622622
// This group represents the API topics that can childs
623623
// of another API topic

mysqlshdk/shellcore/shell_core.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -310,12 +310,12 @@ void Shell_core::execute_module(const std::string &file_name,
310310
_langs[_mode]->execute_module(file_name);
311311
}
312312

313-
bool Shell_core::load_plugin(Mode mode, const std::string &file_name) {
313+
bool Shell_core::load_plugin(Mode mode, const Plugin_definition &plugin) {
314314
assert(mode != Mode::None);
315315

316316
init_mode(mode);
317317

318-
return _langs[mode]->load_plugin(file_name);
318+
return _langs[mode]->load_plugin(plugin);
319319
}
320320

321321
//------------------ COMMAND HANDLER FUNCTIONS ------------------//

mysqlshdk/shellcore/shell_jscript.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,6 @@ std::string Shell_javascript::get_continued_input_context() {
8787
return m_last_input_state == Input_state::Ok ? "" : "-";
8888
}
8989

90-
bool Shell_javascript::load_plugin(const std::string &file_name) {
91-
return _js->load_plugin(file_name);
90+
bool Shell_javascript::load_plugin(const Plugin_definition &plugin) {
91+
return _js->load_plugin(plugin);
9292
}

mysqlshdk/shellcore/shell_python.cc

+2-2
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ void Shell_python::execute_module(const std::string &file_name) {
176176
}
177177
}
178178

179-
bool Shell_python::load_plugin(const std::string &file_name) {
180-
return _py->load_plugin(file_name);
179+
bool Shell_python::load_plugin(const Plugin_definition &plugin) {
180+
return _py->load_plugin(plugin);
181181
}
182182

183183
void Shell_python::clear_input() {

0 commit comments

Comments
 (0)