From 0251711848bfc97b30b57d8d5d8f5d9394d59b4f Mon Sep 17 00:00:00 2001
From: Enric Tejedor Saavedra <enric.tejedor.saavedra@cern.ch>
Date: Wed, 24 Apr 2019 16:14:41 +0200
Subject: [PATCH] [Exp PyROOT] Create an RPyROOTApplication when importing ROOT

The RPyROOTApplication is a TApplication that sets up the nuts
and bolts for interactive ROOT use from Python, closely
following TRint.
---
 .../pyroot_experimental/PyROOT/CMakeLists.txt |   2 +
 .../PyROOT/python/ROOT/__init__.py            |   4 +
 .../PyROOT/python/ROOT/_application.py        |  14 ++
 .../PyROOT/src/PyROOTModule.cxx               |   3 +
 .../PyROOT/src/RPyROOTApplication.cxx         | 151 ++++++++++++++++++
 .../PyROOT/src/RPyROOTApplication.h           |  47 ++++++
 6 files changed, 221 insertions(+)
 create mode 100644 bindings/pyroot_experimental/PyROOT/python/ROOT/_application.py
 create mode 100644 bindings/pyroot_experimental/PyROOT/src/RPyROOTApplication.cxx
 create mode 100644 bindings/pyroot_experimental/PyROOT/src/RPyROOTApplication.h

diff --git a/bindings/pyroot_experimental/PyROOT/CMakeLists.txt b/bindings/pyroot_experimental/PyROOT/CMakeLists.txt
index 459b6a7a645..23e7a1dfe84 100644
--- a/bindings/pyroot_experimental/PyROOT/CMakeLists.txt
+++ b/bindings/pyroot_experimental/PyROOT/CMakeLists.txt
@@ -4,6 +4,7 @@
 
 set(py_sources
   ROOT/__init__.py
+  ROOT/_application.py
   ROOT/_facade.py
   ROOT/pythonization/__init__.py
   ROOT/pythonization/_generic.py
@@ -34,6 +35,7 @@ set(sources
   src/PyROOTModule.cxx
   src/PyROOTStrings.cxx
   src/PyROOTWrapper.cxx
+  src/RPyROOTApplication.cxx
   src/GenericPyz.cxx
   src/RDataFramePyz.cxx
   src/RVecPyz.cxx
diff --git a/bindings/pyroot_experimental/PyROOT/python/ROOT/__init__.py b/bindings/pyroot_experimental/PyROOT/python/ROOT/__init__.py
index b5bcf0a17e8..f4c8156d1d4 100644
--- a/bindings/pyroot_experimental/PyROOT/python/ROOT/__init__.py
+++ b/bindings/pyroot_experimental/PyROOT/python/ROOT/__init__.py
@@ -54,6 +54,10 @@ def pythonization(lazy = True):
 for _, module_name, _ in  pkgutil.walk_packages(pyz.__path__):
     module = importlib.import_module(pyz.__name__ + '.' + module_name)
 
+# Setup interactive usage from Python
+from ._application import create_application
+create_application()
+
 # Configure ROOT facade module
 import sys
 from ._facade import ROOTFacade
diff --git a/bindings/pyroot_experimental/PyROOT/python/ROOT/_application.py b/bindings/pyroot_experimental/PyROOT/python/ROOT/_application.py
new file mode 100644
index 00000000000..3ba0b822ba3
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/python/ROOT/_application.py
@@ -0,0 +1,14 @@
+# Author: Enric Tejedor CERN  04/2019
+
+################################################################################
+# Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.                      #
+# All rights reserved.                                                         #
+#                                                                              #
+# For the licensing terms see $ROOTSYS/LICENSE.                                #
+# For the list of contributors see $ROOTSYS/README/CREDITS.                    #
+################################################################################
+
+from libROOTPython import InitApplication
+
+def create_application():
+    InitApplication()
diff --git a/bindings/pyroot_experimental/PyROOT/src/PyROOTModule.cxx b/bindings/pyroot_experimental/PyROOT/src/PyROOTModule.cxx
index 2f7f15e79b7..9552c42c8f0 100644
--- a/bindings/pyroot_experimental/PyROOT/src/PyROOTModule.cxx
+++ b/bindings/pyroot_experimental/PyROOT/src/PyROOTModule.cxx
@@ -13,6 +13,7 @@
 #include "PyROOTPythonize.h"
 #include "PyROOTStrings.h"
 #include "PyROOTWrapper.h"
+#include "RPyROOTApplication.h"
 
 // Cppyy
 #include "CPyCppyy.h"
@@ -67,6 +68,8 @@ static PyMethodDef gPyROOTMethods[] = {{(char *)"AddDirectoryWritePyz", (PyCFunc
                                         (char *)"Get object with array interface as RVec"},
                                        {(char *)"MakeNumpyDataFrame", (PyCFunction)PyROOT::MakeNumpyDataFrame, METH_O,
                                         (char *)"Make RDataFrame from dictionary of numpy arrays"},
+                                       {(char *)"InitApplication", (PyCFunction)PyROOT::RPyROOTApplication::InitApplication, METH_NOARGS,
+                                        (char *)"Initialize interactive ROOT use from Python"},
                                        {NULL, NULL, 0, NULL}};
 
 #if PY_VERSION_HEX >= 0x03000000
diff --git a/bindings/pyroot_experimental/PyROOT/src/RPyROOTApplication.cxx b/bindings/pyroot_experimental/PyROOT/src/RPyROOTApplication.cxx
new file mode 100644
index 00000000000..116133b450b
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/src/RPyROOTApplication.cxx
@@ -0,0 +1,151 @@
+// Author: Enric Tejedor CERN  04/2019
+// Original PyROOT code by Wim Lavrijsen, LBL
+
+/*************************************************************************
+ * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.               *
+ * All rights reserved.                                                  *
+ *                                                                       *
+ * For the licensing terms see $ROOTSYS/LICENSE.                         *
+ * For the list of contributors see $ROOTSYS/README/CREDITS.             *
+ *************************************************************************/
+
+// Bindings
+#include "Python.h"
+#include "RPyROOTApplication.h"
+
+// ROOT
+#include "TInterpreter.h"
+#include "TSystem.h"
+#include "TBenchmark.h"
+#include "TStyle.h"
+#include "TError.h"
+#include "Getline.h"
+#include "TVirtualMutex.h"
+#ifdef R__WIN32
+#include "TVirtualX.h"
+#endif
+
+////////////////////////////////////////////////////////////////////////////
+/// \brief Create an RPyROOTApplication.
+/// \return false if gApplication is not null, true otherwise.
+bool PyROOT::RPyROOTApplication::CreateApplication()
+{
+   if (!gApplication) {
+      int argc = 1;
+      char **argv = new char *[argc];
+
+      // TODO: Consider parsing arguments for the RPyROOTApplication here
+
+#if PY_VERSION_HEX < 0x03000000
+      if (Py_GetProgramName() && strlen(Py_GetProgramName()) != 0)
+         argv[0] = Py_GetProgramName();
+      else
+         argv[0] = (char *)"python";
+#else
+      argv[0] = (char *)"python";
+#endif
+
+      gApplication = new RPyROOTApplication("PyROOT", &argc, argv);
+      delete[] argv; // TApplication ctor has copied argv, so done with it
+
+      return true;
+   }
+
+   return false;
+}
+
+////////////////////////////////////////////////////////////////////////////
+/// \brief Setup the basic ROOT globals gBenchmark, gStyle and gProgname,
+/// if not already set.
+void PyROOT::RPyROOTApplication::InitROOTGlobals()
+{
+   if (!gBenchmark)
+      gBenchmark = new TBenchmark();
+   if (!gStyle)
+      gStyle = new TStyle();
+
+   if (!gProgName) // should have been set by TApplication
+#if PY_VERSION_HEX < 0x03000000
+      gSystem->SetProgname(Py_GetProgramName());
+#else
+      gSystem->SetProgname("python");
+#endif
+}
+
+////////////////////////////////////////////////////////////////////////////
+/// \brief Translate ROOT error/warning to Python.
+static void ErrMsgHandler(int level, Bool_t abort, const char *location, const char *msg)
+{
+   // Initialization from gEnv (the default handler will return w/o msg b/c level too low)
+   if (gErrorIgnoreLevel == kUnset)
+      ::DefaultErrorHandler(kUnset - 1, kFALSE, "", "");
+
+   if (level < gErrorIgnoreLevel)
+      return;
+
+   // Turn warnings into Python warnings
+   if (level >= kError) {
+      ::DefaultErrorHandler(level, abort, location, msg);
+   } else if (level >= kWarning) {
+      static const char *emptyString = "";
+      if (!location)
+         location = emptyString;
+      // This warning might be triggered while holding the ROOT lock, while
+      // some other thread is holding the GIL and waiting for the ROOT lock.
+      // That will trigger a deadlock.
+      // So if ROOT is in MT mode, use ROOT's error handler that doesn't take
+      // the GIL.
+      if (!gGlobalMutex) {
+         // Either printout or raise exception, depending on user settings
+         PyErr_WarnExplicit(NULL, (char *)msg, (char *)location, 0, (char *)"ROOT", NULL);
+      } else {
+         ::DefaultErrorHandler(level, abort, location, msg);
+      }
+   } else {
+      ::DefaultErrorHandler(level, abort, location, msg);
+   }
+}
+
+////////////////////////////////////////////////////////////////////////////
+/// \brief Install the ROOT message handler which will turn ROOT error
+/// messages into Python exceptions.
+void PyROOT::RPyROOTApplication::InitROOTMessageCallback()
+{
+   SetErrorHandler((ErrorHandlerFunc_t)&ErrMsgHandler);
+}
+
+////////////////////////////////////////////////////////////////////////////
+/// \brief Initialize an RPyROOTApplication.
+PyObject *PyROOT::RPyROOTApplication::InitApplication()
+{
+   if (CreateApplication()) {
+      InitROOTGlobals();
+      InitROOTMessageCallback();
+   }
+
+   Py_RETURN_NONE;
+}
+
+////////////////////////////////////////////////////////////////////////////
+/// \brief Construct a TApplication for PyROOT.
+/// \param[in] acn Application class name.
+/// \param[in] argc Number of arguments.
+/// \param[in] argv Arguments.
+PyROOT::RPyROOTApplication::RPyROOTApplication(const char *acn, int *argc, char **argv) : TApplication(acn, argc, argv)
+{
+#ifdef WIN32
+   // Switch win32 proxy main thread id
+   if (gVirtualX)
+      ProcessLine("((TGWin32 *)gVirtualX)->SetUserThreadId(0);", true);
+#endif
+
+   // Save current interpreter context
+   gInterpreter->SaveContext();
+   gInterpreter->SaveGlobalsContext();
+
+   // Prevent crashes on accessing history
+   Gl_histinit((char *)"-");
+
+   // Prevent ROOT from exiting python
+   SetReturnFromRun(true);
+}
diff --git a/bindings/pyroot_experimental/PyROOT/src/RPyROOTApplication.h b/bindings/pyroot_experimental/PyROOT/src/RPyROOTApplication.h
new file mode 100644
index 00000000000..c116cf36014
--- /dev/null
+++ b/bindings/pyroot_experimental/PyROOT/src/RPyROOTApplication.h
@@ -0,0 +1,47 @@
+// Author: Enric Tejedor CERN  04/2019
+// Original PyROOT code by Wim Lavrijsen, LBL
+
+/*************************************************************************
+ * Copyright (C) 1995-2019, Rene Brun and Fons Rademakers.               *
+ * All rights reserved.                                                  *
+ *                                                                       *
+ * For the licensing terms see $ROOTSYS/LICENSE.                         *
+ * For the list of contributors see $ROOTSYS/README/CREDITS.             *
+ *************************************************************************/
+
+#ifndef ROOT_PyROOTApplication
+#define ROOT_PyROOTApplication
+
+// ROOT
+#include "TApplication.h"
+
+namespace PyROOT {
+
+// clang-format off
+/**
+\class PyROOT::RPyROOTApplication
+\brief Interactive application for Python.
+
+ The RPyROOTApplication sets up the nuts and bolts for interactive ROOT use
+ from Python, closely following TRint. Note that not everything is done here,
+ some bits (such as e.g. the use of exception hook for shell escapes) are more
+ easily done in Python.
+*/
+// clang-format on
+
+class RPyROOTApplication : public TApplication {
+public:
+   static PyObject *InitApplication();
+
+   RPyROOTApplication(const char *acn, int *argc, char **argv);
+   virtual ~RPyROOTApplication() {}
+
+private:
+   static bool CreateApplication();
+   static void InitROOTGlobals();
+   static void InitROOTMessageCallback();
+};
+
+} // namespace PyROOT
+
+#endif
-- 
GitLab