Browse Source

Throw ImportError instead of cryptic failure when importing extension module from incompatible version of Python

rdb 10 years ago
parent
commit
33d183d4f7
2 changed files with 55 additions and 24 deletions
  1. 40 16
      dtool/src/interrogate/interrogate_module.cxx
  2. 15 8
      dtool/src/interrogatedb/py_panda.cxx

+ 40 - 16
dtool/src/interrogate/interrogate_module.cxx

@@ -128,6 +128,28 @@ int write_python_table_native(ostream &out) {
     out << "extern void Dtool_" << *ii << "_BuildInstants(PyObject *module);\n";
   }
 
+  out.put('\n');
+
+  out << "#if PY_MAJOR_VERSION >= 3 || !defined(NDEBUG)\n"
+      << "#ifdef _WIN32\n"
+      << "extern \"C\" __declspec(dllexport) PyObject *PyInit_" << library_name << "();\n"
+      << "#elif __GNUC__ >= 4\n"
+      << "extern \"C\" __attribute__((visibility(\"default\"))) PyObject *PyInit_" << library_name << "();\n"
+      << "#else\n"
+      << "extern \"C\" PyObject *PyInit_" << library_name << "();\n"
+      << "#endif\n"
+      << "#endif\n";
+
+  out << "#if PY_MAJOR_VERSION < 3 || !defined(NDEBUG)\n"
+      << "#ifdef _WIN32\n"
+      << "extern \"C\" __declspec(dllexport) void init" << library_name << "();\n"
+      << "#elif __GNUC__ >= 4\n"
+      << "extern \"C\" __attribute__((visibility(\"default\"))) void init" << library_name << "();\n"
+      << "#else\n"
+      << "extern \"C\" void init" << library_name << "();\n"
+      << "#endif\n"
+      << "#endif\n";
+
   out << "\n"
       << "#if PY_MAJOR_VERSION >= 3\n"
       << "static struct PyModuleDef py_" << library_name << "_module = {\n"
@@ -139,14 +161,6 @@ int write_python_table_native(ostream &out) {
       << "  NULL, NULL, NULL, NULL\n"
       << "};\n"
       << "\n"
-      << "#ifdef _WIN32\n"
-      << "extern \"C\" __declspec(dllexport) PyObject *PyInit_" << library_name << "();\n"
-      << "#elif __GNUC__ >= 4\n"
-      << "extern \"C\" __attribute__((visibility(\"default\"))) PyObject *PyInit_" << library_name << "();\n"
-      << "#else\n"
-      << "extern \"C\" PyObject *PyInit_" << library_name << "();\n"
-      << "#endif\n"
-      << "\n"
       << "PyObject *PyInit_" << library_name << "() {\n";
 
   if (track_interpreter) {
@@ -184,15 +198,16 @@ int write_python_table_native(ostream &out) {
       << "  return module;\n"
       << "}\n"
       << "\n"
-      << "#else  // Python 2 case\n"
-      << "\n"
-      << "#ifdef _WIN32\n"
-      << "extern \"C\" __declspec(dllexport) void init" << library_name << "();\n"
-      << "#elif __GNUC__ >= 4\n"
-      << "extern \"C\" __attribute__((visibility(\"default\"))) void init" << library_name << "();\n"
-      << "#else\n"
-      << "extern \"C\" void init" << library_name << "();\n"
+
+      << "#ifndef NDEBUG\n"
+      << "void init" << library_name << "() {\n"
+      << "  PyErr_SetString(PyExc_ImportError, \"" << module_name << " was "
+      << "compiled for Python \" PY_VERSION \", which is incompatible "
+      << "with Python 2\");\n"
+      << "}\n"
       << "#endif\n"
+
+      << "#else  // Python 2 case\n"
       << "\n"
       << "void init" << library_name << "() {\n";
 
@@ -228,6 +243,15 @@ int write_python_table_native(ostream &out) {
 
   out << "  }\n"
       << "}\n"
+      << "\n"
+      << "#ifndef NDEBUG\n"
+      << "PyObject *PyInit_" << library_name << "() {\n"
+      << "  PyErr_SetString(PyExc_ImportError, \"" << module_name << " was "
+      << "compiled for Python \" PY_VERSION \", which is incompatible "
+      << "with Python 3\");\n"
+      << "  return (PyObject *)NULL;\n"
+      << "}\n"
+      << "#endif\n"
       << "#endif\n"
       << "\n";
 

+ 15 - 8
dtool/src/interrogatedb/py_panda.cxx

@@ -597,17 +597,18 @@ PyObject *Dtool_PyModuleInitHelper(LibraryDef *defs[], const char *modulename) {
 #endif
   // Check the version so we can print a helpful error if it doesn't match.
   string version = Py_GetVersion();
-  if (interrogatedb_cat.is_debug()) {
-    interrogatedb_cat.debug()
-      << "Python " << version << "\n";
-  }
 
   if (version[0] != '0' + PY_MAJOR_VERSION ||
       version[2] != '0' + PY_MINOR_VERSION) {
-    interrogatedb_cat.error()
-      << "This module was compiled for Python "
-      << PY_MAJOR_VERSION << "." << PY_MINOR_VERSION << ", which is incompatible "
-      << "with Python " << version << ".\n";
+    // Raise a helpful error message.  We can safely do this because the
+    // signature and behavior for PyErr_SetString has remained consistent.
+    ostringstream errs;
+    errs << "this module was compiled for Python "
+         << PY_MAJOR_VERSION << "." << PY_MINOR_VERSION << ", which is "
+         << "incompatible with Python " << version.substr(0, 3);
+    string error = errs.str();
+    PyErr_SetString(PyExc_ImportError, error.c_str());
+    return (PyObject *)NULL;
   }
 
   // Initialize the base class of everything.
@@ -649,6 +650,12 @@ PyObject *Dtool_PyModuleInitHelper(LibraryDef *defs[], const char *modulename) {
   // to do that.  Perhaps we'll find a better place for this in the future.
   static bool initialized_main_dir = false;
   if (!initialized_main_dir) {
+    if (interrogatedb_cat.is_debug()) {
+      // Good opportunity to print this out once, at startup.
+      interrogatedb_cat.debug()
+        << "Python " << version << "\n";
+    }
+
     // Grab the __main__ module.
     PyObject *main_module = PyImport_ImportModule("__main__");
     if (main_module == NULL) {