LibreOffice Module pyuno (master)  1
pyuno_loader.cxx
Go to the documentation of this file.
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3  * This file is part of the LibreOffice project.
4  *
5  * This Source Code Form is subject to the terms of the Mozilla Public
6  * License, v. 2.0. If a copy of the MPL was not distributed with this
7  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8  *
9  * This file incorporates work covered by the following license notice:
10  *
11  * Licensed to the Apache Software Foundation (ASF) under one or more
12  * contributor license agreements. See the NOTICE file distributed
13  * with this work for additional information regarding copyright
14  * ownership. The ASF licenses this file to you under the Apache
15  * License, Version 2.0 (the "License"); you may not use this file
16  * except in compliance with the License. You may obtain a copy of
17  * the License at http://www.apache.org/licenses/LICENSE-2.0 .
18  */
19 
20 #include <config_features.h>
21 #include <config_folders.h>
22 
23 #include <pyuno.hxx>
24 
25 #include <o3tl/any.hxx>
26 
27 #include <osl/process.h>
28 #include <osl/file.hxx>
29 #include <osl/thread.h>
30 
31 #include <rtl/ustrbuf.hxx>
32 #include <rtl/bootstrap.hxx>
33 
35 #include <cppuhelper/factory.hxx>
36 
37 #include <com/sun/star/uno/XComponentContext.hpp>
38 
39 // apparently PATH_MAX is not standard and not defined by MSVC
40 #ifndef PATH_MAX
41 #ifdef _MAX_PATH
42 #define PATH_MAX _MAX_PATH
43 #else
44 #ifdef MAX_PATH
45 #define PATH_MAX MAX_PATH
46 #else
47 #error no PATH_MAX
48 #endif
49 #endif
50 #endif
51 
52 using pyuno::PyRef;
53 using pyuno::NOT_NULL;
54 using pyuno::Runtime;
56 
58 using com::sun::star::uno::XInterface;
60 using com::sun::star::uno::XComponentContext;
61 using com::sun::star::uno::RuntimeException;
62 
63 namespace pyuno_loader
64 {
65 
68 {
69  if( PyErr_Occurred() )
70  {
71  PyRef excType, excValue, excTraceback;
72  PyErr_Fetch(reinterpret_cast<PyObject **>(&excType), reinterpret_cast<PyObject**>(&excValue), reinterpret_cast<PyObject**>(&excTraceback));
74  css::uno::Any a = runtime.extractUnoException( excType, excValue, excTraceback );
75  OUStringBuffer buf;
76  buf.append( "python-loader:" );
77  if( auto e = o3tl::tryAccess<css::uno::Exception>(a) )
78  buf.append( e->Message );
79  throw RuntimeException( buf.makeStringAndClear() );
80  }
81 }
82 
85 {
86  PyRef module(
87  PyImport_ImportModule( "pythonloader" ),
88  SAL_NO_ACQUIRE );
90  if( !module.is() )
91  {
92  throw RuntimeException( "pythonloader: Couldn't load pythonloader module" );
93  }
94  return PyRef( PyModule_GetDict( module.get() ));
95 }
96 
98 static PyRef getObjectFromLoaderModule( const char * func )
99 {
100  PyRef object( PyDict_GetItemString(getLoaderModule().get(), func ) );
101  if( !object.is() )
102  {
103  throw RuntimeException( "pythonloader: couldn't find core element pythonloader." +
104  OUString::createFromAscii( func ));
105  }
106  return object;
107 }
108 
109 static void setPythonHome ( const OUString & pythonHome )
110 {
111  OUString systemPythonHome;
112  osl_getSystemPathFromFileURL( pythonHome.pData, &(systemPythonHome.pData) );
113  OString o = OUStringToOString( systemPythonHome, osl_getThreadTextEncoding() );
114  // static because Py_SetPythonHome just copies the "wide" pointer
115  static wchar_t wide[PATH_MAX + 1];
116  size_t len = mbstowcs(wide, o.pData->buffer, PATH_MAX + 1);
117  if(len == size_t(-1))
118  {
119  PyErr_SetString(PyExc_SystemError, "invalid multibyte sequence in python home path");
120  return;
121  }
122  if(len == PATH_MAX + 1)
123  {
124  PyErr_SetString(PyExc_SystemError, "python home path is too long");
125  return;
126  }
127  Py_SetPythonHome(wide);
128 }
129 
130 static void prependPythonPath( const OUString & pythonPathBootstrap )
131 {
132  OUStringBuffer bufPYTHONPATH( 256 );
133  bool bAppendSep = false;
134  sal_Int32 nIndex = 0;
135  while( true )
136  {
137  sal_Int32 nNew = pythonPathBootstrap.indexOf( ' ', nIndex );
138  OUString fileUrl;
139  if( nNew == -1 )
140  {
141  fileUrl = pythonPathBootstrap.copy(nIndex);
142  }
143  else
144  {
145  fileUrl = pythonPathBootstrap.copy(nIndex, nNew - nIndex);
146  }
147  OUString systemPath;
148  osl_getSystemPathFromFileURL( fileUrl.pData, &(systemPath.pData) );
149  if (!systemPath.isEmpty())
150  {
151  if (bAppendSep)
152  bufPYTHONPATH.append(static_cast<sal_Unicode>(SAL_PATHSEPARATOR));
153  bufPYTHONPATH.append(systemPath);
154  bAppendSep = true;
155  }
156  if( nNew == -1 )
157  break;
158  nIndex = nNew + 1;
159  }
160  const char * oldEnv = getenv( "PYTHONPATH");
161  if( oldEnv )
162  {
163  if (bAppendSep)
164  bufPYTHONPATH.append( static_cast<sal_Unicode>(SAL_PATHSEPARATOR) );
165  bufPYTHONPATH.append( OUString(oldEnv, strlen(oldEnv), osl_getThreadTextEncoding()) );
166  }
167 
168  OUString envVar("PYTHONPATH");
169  OUString envValue(bufPYTHONPATH.makeStringAndClear());
170  osl_setEnvironment(envVar.pData, envValue.pData);
171 }
172 
173 namespace {
174 
175 struct PythonInit
176 {
177 PythonInit() {
178  if ( Py_IsInitialized()) // may be inited by getComponentContext() already
179  return;
180 
181  OUString pythonPath;
182  OUString pythonHome;
183  OUString path( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("pythonloader.uno" ));
184  rtl::Bootstrap::expandMacros(path); //TODO: detect failure
185  rtl::Bootstrap bootstrap(path);
186 
187  // look for pythonhome
188  bootstrap.getFrom( "PYUNO_LOADER_PYTHONHOME", pythonHome );
189  bootstrap.getFrom( "PYUNO_LOADER_PYTHONPATH", pythonPath );
190 
191  // pythonhome+pythonpath must be set before Py_Initialize(), otherwise there appear warning on the console
192  // sadly, there is no api for setting the pythonpath, we have to use the environment variable
193  if( !pythonHome.isEmpty() )
194  setPythonHome( pythonHome );
195 
196  if( !pythonPath.isEmpty() )
197  prependPythonPath( pythonPath );
198 
199 #ifdef _WIN32
200  //extend PATH under windows to include the branddir/program so ssl libs will be found
201  //for use by terminal mailmerge dependency _ssl.pyd
202  OUString sEnvName("PATH");
203  OUString sPath;
204  osl_getEnvironment(sEnvName.pData, &sPath.pData);
205  OUString sBrandLocation("$BRAND_BASE_DIR/program");
206  rtl::Bootstrap::expandMacros(sBrandLocation);
207  osl::FileBase::getSystemPathFromFileURL(sBrandLocation, sBrandLocation);
208  sPath = OUStringBuffer(sPath).
209  append(static_cast<sal_Unicode>(SAL_PATHSEPARATOR)).
210  append(sBrandLocation).makeStringAndClear();
211  osl_setEnvironment(sEnvName.pData, sPath.pData);
212 #endif
213 
214  PyImport_AppendInittab( "pyuno", PyInit_pyuno );
215 
216 #if HAVE_FEATURE_READONLY_INSTALLSET
217  Py_DontWriteBytecodeFlag = 1;
218 #endif
219 
220  // initialize python
221  Py_Initialize();
222  PyEval_InitThreads();
223 
224  PyThreadState *tstate = PyThreadState_Get();
225  PyEval_ReleaseThread( tstate );
226  // This tstate is never used again, so delete it here.
227  // This prevents an assertion in PyThreadState_Swap on the
228  // PyThreadAttach below.
229  PyThreadState_Delete(tstate);
230 }
231 };
232 
233 }
234 
235 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
237  css::uno::XComponentContext* ctx , css::uno::Sequence<css::uno::Any> const&)
238 {
239  // tdf#114815 thread-safe static to init python only once
240  static PythonInit s_Init;
241 
243 
244  PyThreadAttach attach( PyInterpreterState_Head() );
245  {
246  // note: this can't race against getComponentContext() because
247  // either (in soffice.bin) CreateInstance() must be called before
248  // getComponentContext() can be called, or (in python.bin) the other
249  // way around
250  if( ! Runtime::isInitialized() )
251  {
252  Runtime::initialize( ctx );
253  }
255 
256  PyRef pyCtx = runtime.any2PyObject(
257  css::uno::makeAny( css::uno::Reference(ctx) ) );
258 
259  PyRef clazz = getObjectFromLoaderModule( "Loader" );
260  PyRef args ( PyTuple_New( 1 ), SAL_NO_ACQUIRE, NOT_NULL );
261  PyTuple_SetItem( args.get(), 0 , pyCtx.getAcquired() );
262  PyRef pyInstance( PyObject_CallObject( clazz.get() , args.get() ), SAL_NO_ACQUIRE );
263  runtime.pyObject2Any( pyInstance ) >>= ret;
264  }
265  ret->acquire();
266  return ret.get();
267 }
268 
269 }
270 
271 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
sal_Int32 nIndex
#define SAL_PATHSEPARATOR
Helper class for keeping references to python objects.
Definition: pyuno.hxx:79
helper class for attaching the current thread to the python runtime.
Definition: pyuno.hxx:271
static void prependPythonPath(const OUString &pythonPathBootstrap)
tuple args
exports com.sun.star.form. runtime
def bootstrap()
Definition: officehelper.py:37
PyObject * getAcquired() const
Definition: pyuno.hxx:101
static void raiseRuntimeExceptionWhenNeeded()
css::uno::Any extractUnoException(const PyRef &excType, const PyRef &excValue, const PyRef &excTraceback) const
extracts a proper uno exception from a given python exception
#define SAL_CONFIGFILE(name)
SAL_DLLPUBLIC_EXPORT css::uno::XInterface * pyuno_Loader_get_implementation(css::uno::XComponentContext *ctx, css::uno::Sequence< css::uno::Any > const &)
uno_Any a
static PyRef getObjectFromLoaderModule(const char *func)
static void setPythonHome(const OUString &pythonHome)
OString OUStringToOString(const OUString &str, ConnectionSettings const *settings)
bool is() const
returns 1 when the reference points to a python object python object, otherwise 0.
Definition: pyuno.hxx:139
static PyRef getLoaderModule()
PyObject * get() const noexcept
Definition: pyuno.hxx:99
SAL_DLLPUBLIC_EXPORT PyObject * PyInit_pyuno(void)
function called by the python runtime to initialize the pyuno module.
css::uno::Any pyObject2Any(const PyRef &source, enum ConversionMode mode=REJECT_UNO_ANY) const
converts a Python object to a UNO any
definition of a no acquire enum for ctors
Definition: pyuno.hxx:65
The pyuno::Runtime class keeps the internal state of the python UNO bridge for the currently in use p...
Definition: pyuno.hxx:163
PyRef any2PyObject(const css::uno::Any &source) const
converts something contained in a UNO Any to a Python object