lawrence_danna created this revision.
lawrence_danna added reviewers: jasonmolenda, JDevlieghere, jingham.
Herald added subscribers: dang, mgorny.
lawrence_danna requested review of this revision.
Herald added a project: LLDB.

It is surprisingly difficult to write a simple python script that
can reliably `import lldb` without failing, or crashing.   I'm
currently resorting to convolutions like this:

def find_lldb(may_reexec=False):

                if prefix := os.environ.get('LLDB_PYTHON_PREFIX'):
                        if os.path.realpath(prefix) != 
os.path.realpath(sys.prefix):
                                raise Exception("cannot import lldb.\n"
                                        f"  sys.prefix should be: {prefix}\n"
                                        f"  but it is: {sys.prefix}")
                else:
                        line1, line2 = subprocess.run(
                                ['lldb', '-x', '-b', '-o', 'script 
print(sys.prefix)'],
                                encoding='utf8', stdout=subprocess.PIPE,
                                check=True).stdout.strip().splitlines()
                        assert line1.strip() == '(lldb) script 
print(sys.prefix)'
                        prefix = line2.strip()
                        os.environ['LLDB_PYTHON_PREFIX'] = prefix
  
                if sys.prefix != prefix:
                        if not may_reexec:
                                raise Exception(
                                        "cannot import lldb.\n" +
                                        f"  This python, at {sys.prefix}\n"
                                        f"  does not math LLDB's python at 
{prefix}")
                        os.environ['LLDB_PYTHON_PREFIX'] = prefix
                        python_exe = os.path.join(prefix, 'bin', 'python3')
                        os.execl(python_exe, python_exe, *sys.argv)
  
                lldb_path = subprocess.run(['lldb', '-P'],
                        check=True, stdout=subprocess.PIPE,
                                encoding='utf8').stdout.strip()
  
                sys.path = [lldb_path] + sys.path

This patch aims to replace all that with:

  #!/usr/bin/env lldb-python
  import lldb
  ...

- by adding the following features:

- new lldb::PathType for SBHostOS::GetLLDBPath: ePathTypePythonPrefix

is the path to python's sys.prefix

- new command line option: lldb --python-prefix to print that prefix.

- new tool (unix only): lldb-python which finds python and exec's it.


Repository:
  rG LLVM Github Monorepo

https://reviews.llvm.org/D112973

Files:
  lldb/bindings/python/CMakeLists.txt
  lldb/docs/man/lldb.rst
  lldb/docs/python_api_enums.rst
  lldb/include/lldb/lldb-enumerations.h
  lldb/source/API/SBHostOS.cpp
  lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
  lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h
  lldb/test/API/functionalities/paths/TestPaths.py
  lldb/test/Shell/Driver/TestHelp.test
  lldb/tools/driver/Driver.cpp
  lldb/tools/driver/Driver.h
  lldb/tools/driver/Options.td

Index: lldb/tools/driver/Options.td
===================================================================
--- lldb/tools/driver/Options.td
+++ lldb/tools/driver/Options.td
@@ -48,6 +48,10 @@
   HelpText<"Alias for --python-path">,
   Group<grp_scripting>;
 
+def python_prefix: F<"python-prefix">,
+  HelpText<"Prints out the sys.prefix for the python used by this lldb.">,
+  Group<grp_scripting>;
+
 def script_language: Separate<["--", "-"], "script-language">,
   MetaVarName<"<language>">,
   HelpText<"Tells the debugger to use the specified scripting language for user-defined scripts.">,
Index: lldb/tools/driver/Driver.h
===================================================================
--- lldb/tools/driver/Driver.h
+++ lldb/tools/driver/Driver.h
@@ -79,6 +79,7 @@
     bool m_source_quietly = false;
     bool m_print_version = false;
     bool m_print_python_path = false;
+    bool m_print_python_prefix = false;
     bool m_wait_for = false;
     bool m_repl = false;
     bool m_batch = false;
Index: lldb/tools/driver/Driver.cpp
===================================================================
--- lldb/tools/driver/Driver.cpp
+++ lldb/tools/driver/Driver.cpp
@@ -43,7 +43,7 @@
 #include <cstring>
 #include <fcntl.h>
 
-// Includes for pipe()
+// Includes for pipe(), execv()
 #if defined(_WIN32)
 #include <fcntl.h>
 #include <io.h>
@@ -201,6 +201,9 @@
   if (args.hasArg(OPT_python_path)) {
     m_option_data.m_print_python_path = true;
   }
+  if (args.hasArg(OPT_python_prefix)) {
+    m_option_data.m_print_python_prefix = true;
+  }
 
   if (args.hasArg(OPT_batch)) {
     m_option_data.m_batch = true;
@@ -398,6 +401,21 @@
     return error;
   }
 
+  if (m_option_data.m_print_python_prefix) {
+    SBFileSpec python_file_spec = SBHostOS::GetLLDBPath(ePathTypePythonPrefix);
+    if (python_file_spec.IsValid()) {
+      char python_path[PATH_MAX];
+      size_t num_chars = python_file_spec.GetPath(python_path, PATH_MAX);
+      if (num_chars < PATH_MAX) {
+        llvm::outs() << python_path << '\n';
+      } else
+        llvm::outs() << "<PATH TOO LONG>\n";
+    } else
+      llvm::outs() << "<COULD NOT FIND PATH>\n";
+    exiting = true;
+    return error;
+  }
+
   return error;
 }
 
@@ -867,7 +885,49 @@
   return llvm::None;
 }
 
-int main(int argc, char const *argv[]) {
+static bool should_exec_python(llvm::StringRef argv0) {
+#if defined(_WIN32)
+  return false;
+#else
+  return argv0 == "lldb-python" || argv0.endswith("/lldb-python");
+#endif
+}
+
+static void exec_python(int argc, char *argv[]) {
+#if !defined(_WIN32)
+  SBFileSpec pathspec = SBHostOS::GetLLDBPythonPath();
+  if (!pathspec.IsValid()) {
+    llvm::errs() << "could not find python path.\n";
+    exit(1);
+  }
+  std::string pythonpath = pathspec.GetDirectory();
+  const char *oldpath = ::getenv("PYTHONPATH");
+  if (oldpath)
+    pythonpath = pythonpath + ":" + oldpath;
+  ::setenv("PYTHONPATH", pythonpath.c_str(), true);
+  SBFileSpec spec = SBHostOS::GetLLDBPath(ePathTypePythonPrefix);
+  if (!spec.IsValid()) {
+    llvm::errs() << "could not find python prefix.\n";
+    exit(1);
+  }
+  spec.AppendPathComponent("bin");
+  spec.AppendPathComponent("python3");
+  char buf[PATH_MAX];
+  size_t num_chars = spec.GetPath(buf, PATH_MAX);
+  if (num_chars >= PATH_MAX) {
+    llvm::errs() << "path too long.\n";
+    exit(1);
+  }
+  char *python_exe = buf;
+  argv[0] = python_exe;
+  ::execv(python_exe, argv);
+  llvm::errs() << "could not exec python.\n";
+  exit(1);
+#endif
+}
+
+int main(int argc, char *argv[]) {
+
   // Editline uses for example iswprint which is dependent on LC_CTYPE.
   std::setlocale(LC_ALL, "");
   std::setlocale(LC_CTYPE, "");
@@ -877,17 +937,19 @@
   llvm::InitLLVM IL(argc, argv, /*InstallPipeSignalExitHandler=*/false);
 
   // Parse arguments.
-  LLDBOptTable T;
-  unsigned MissingArgIndex;
-  unsigned MissingArgCount;
-  ArrayRef<const char *> arg_arr = makeArrayRef(argv + 1, argc - 1);
-  opt::InputArgList input_args =
-      T.ParseArgs(arg_arr, MissingArgIndex, MissingArgCount);
   llvm::StringRef argv0 = llvm::sys::path::filename(argv[0]);
-
-  if (input_args.hasArg(OPT_help)) {
-    printHelp(T, argv0);
-    return 0;
+  bool will_exec_python = should_exec_python(argv0);
+  unsigned MissingArgIndex = 0;
+  unsigned MissingArgCount = 0;
+  ArrayRef<const char *> arg_arr = makeArrayRef(argv + 1, argc - 1);
+  opt::InputArgList input_args;
+  if (!will_exec_python) {
+    LLDBOptTable T;
+    input_args = T.ParseArgs(arg_arr, MissingArgIndex, MissingArgCount);
+    if (input_args.hasArg(OPT_help)) {
+      printHelp(T, argv0);
+      return 0;
+    }
   }
 
   // Check for missing argument error.
@@ -920,6 +982,9 @@
   }
   SBHostOS::ThreadCreated("<lldb.driver.main-thread>");
 
+  if (will_exec_python)
+    exec_python(argc, argv);
+
   signal(SIGINT, sigint_handler);
 #if !defined(_MSC_VER)
   signal(SIGPIPE, SIG_IGN);
Index: lldb/test/Shell/Driver/TestHelp.test
===================================================================
--- lldb/test/Shell/Driver/TestHelp.test
+++ lldb/test/Shell/Driver/TestHelp.test
@@ -63,5 +63,6 @@
 CHECK: SCRIPTING
 CHECK: -l
 CHECK: --python-path
+CHECK: --python-prefix
 CHECK: -P
 CHECK: --script-language
Index: lldb/test/API/functionalities/paths/TestPaths.py
===================================================================
--- lldb/test/API/functionalities/paths/TestPaths.py
+++ lldb/test/API/functionalities/paths/TestPaths.py
@@ -5,6 +5,7 @@
 
 import lldb
 import os
+import sys
 from lldbsuite.test.decorators import *
 from lldbsuite.test.lldbtest import *
 from lldbsuite.test import lldbutil
@@ -22,10 +23,12 @@
                           lldb.ePathTypeSupportExecutableDir,
                           lldb.ePathTypeHeaderDir,
                           lldb.ePathTypePythonDir,
+                          lldb.ePathTypePythonPrefix,
                           lldb.ePathTypeLLDBSystemPlugins,
                           lldb.ePathTypeLLDBUserPlugins,
                           lldb.ePathTypeLLDBTempSystemDir,
-                          lldb.ePathTypeClangDir]
+                          lldb.ePathTypeClangDir,
+                          lldb.ePathTypePythonPrefix]
 
         for path_type in dir_path_types:
             f = lldb.SBHostOS.GetLLDBPath(path_type)
@@ -42,6 +45,10 @@
         self.assertTrue(any([os.path.exists(os.path.join(shlib_dir, f)) for f in
             filenames]), "shlib_dir = " + shlib_dir)
 
+    @no_debug_info_test
+    def test_prefix(self):
+        prefix = lldb.SBHostOS.GetLLDBPath(lldb.ePathTypePythonPrefix).GetDirectory()
+        self.assertEqual(os.path.realpath(sys.prefix), os.path.realpath(prefix))
 
     @no_debug_info_test
     def test_directory_doesnt_end_with_slash(self):
Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h
+++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.h
@@ -51,6 +51,7 @@
   static llvm::StringRef GetPluginNameStatic() { return "script-python"; }
   static llvm::StringRef GetPluginDescriptionStatic();
   static FileSpec GetPythonDir();
+  static FileSpec GetPythonPrefix();
   static void SharedLibraryDirectoryHelper(FileSpec &this_file);
 
 protected:
Index: lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
===================================================================
--- lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
+++ lldb/source/Plugins/ScriptInterpreter/Python/ScriptInterpreterPython.cpp
@@ -410,6 +410,20 @@
   return g_spec;
 }
 
+FileSpec ScriptInterpreterPython::GetPythonPrefix() {
+  FileSpec spec;
+  InitializePythonRAII initialize_guard;
+  wchar_t *pyhome = Py_GetPrefix();
+  if (pyhome) {
+    char *pyhome8 = Py_EncodeLocale(pyhome, NULL);
+    if (pyhome8) {
+      spec.GetDirectory().SetString(pyhome8);
+      PyMem_Free(pyhome8);
+    }
+  }
+  return spec;
+}
+
 void ScriptInterpreterPython::SharedLibraryDirectoryHelper(
     FileSpec &this_file) {
   // When we're loaded from python, this_file will point to the file inside the
Index: lldb/source/API/SBHostOS.cpp
===================================================================
--- lldb/source/API/SBHostOS.cpp
+++ lldb/source/API/SBHostOS.cpp
@@ -63,6 +63,11 @@
   case ePathTypePythonDir:
 #if LLDB_ENABLE_PYTHON
     fspec = ScriptInterpreterPython::GetPythonDir();
+#endif
+    break;
+  case ePathTypePythonPrefix:
+#if LLDB_ENABLE_PYTHON
+    fspec = ScriptInterpreterPython::GetPythonPrefix();
 #endif
     break;
   case ePathTypeLLDBSystemPlugins:
Index: lldb/include/lldb/lldb-enumerations.h
===================================================================
--- lldb/include/lldb/lldb-enumerations.h
+++ lldb/include/lldb/lldb-enumerations.h
@@ -1011,7 +1011,8 @@
   ePathTypeGlobalLLDBTempSystemDir, ///< The LLDB temp directory for this
                                     ///< system, NOT cleaned up on a process
                                     ///< exit.
-  ePathTypeClangDir ///< Find path to Clang builtin headers
+  ePathTypeClangDir,                ///< Find path to Clang builtin headers
+  ePathTypePythonPrefix,            ///< Find python's  sys.prefix path
 };
 
 /// Kind of member function.
Index: lldb/docs/python_api_enums.rst
===================================================================
--- lldb/docs/python_api_enums.rst
+++ lldb/docs/python_api_enums.rst
@@ -1291,6 +1291,10 @@
 
    Find Python modules (PYTHONPATH) directory.
 
+.. py:data:: ePathTypePythonPrefix
+
+   Find Python's ``sys.prefix`` path.
+
 .. py:data:: ePathTypeLLDBSystemPlugins
 
    System plug-ins directory
Index: lldb/docs/man/lldb.rst
===================================================================
--- lldb/docs/man/lldb.rst
+++ lldb/docs/man/lldb.rst
@@ -238,6 +238,10 @@
 
  Prints out the path to the lldb.py file for this version of lldb.
 
+.. option:: --python-prefix
+
+ Prints out the sys.prefix for the python used by this lldb.
+
 .. option:: -P
 
  Alias for --python-path
Index: lldb/bindings/python/CMakeLists.txt
===================================================================
--- lldb/bindings/python/CMakeLists.txt
+++ lldb/bindings/python/CMakeLists.txt
@@ -149,6 +149,12 @@
   create_relative_symlink(${swig_target} ${LIBLLDB_SYMLINK_DEST}
                           ${lldb_python_target_dir} ${LIBLLDB_SYMLINK_OUTPUT_FILE})
 
+  if (NOT WIN32)
+  add_custom_command(TARGET ${swig_target} POST_BUILD VERBATIM
+   COMMAND ${CMAKE_COMMAND} -E "create_symlink" "lldb" "lldb-python"
+   WORKING_DIRECTORY ${LLVM_RUNTIME_OUTPUT_INTDIR})
+  endif()
+
   if(NOT LLDB_BUILD_FRAMEWORK)
     set(LLDB_ARGDUMPER_FILENAME "lldb-argdumper${CMAKE_EXECUTABLE_SUFFIX}")
     create_relative_symlink(${swig_target} "${LLVM_RUNTIME_OUTPUT_INTDIR}/${LLDB_ARGDUMPER_FILENAME}"
_______________________________________________
lldb-commits mailing list
lldb-commits@lists.llvm.org
https://lists.llvm.org/cgi-bin/mailman/listinfo/lldb-commits

Reply via email to