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));
73  Runtime runtime;
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 OUString getImplementationName()
110 {
111  return "org.openoffice.comp.pyuno.Loader";
112 }
113 
115 {
116  return { "com.sun.star.loader.Python" };
117 }
118 
119 static void setPythonHome ( const OUString & pythonHome )
120 {
121  OUString systemPythonHome;
122  osl_getSystemPathFromFileURL( pythonHome.pData, &(systemPythonHome.pData) );
123  OString o = OUStringToOString( systemPythonHome, osl_getThreadTextEncoding() );
124 #if PY_MAJOR_VERSION >= 3
125  // static because Py_SetPythonHome just copies the "wide" pointer
126  static wchar_t wide[PATH_MAX + 1];
127  size_t len = mbstowcs(wide, o.pData->buffer, PATH_MAX + 1);
128  if(len == size_t(-1))
129  {
130  PyErr_SetString(PyExc_SystemError, "invalid multibyte sequence in python home path");
131  return;
132  }
133  if(len == PATH_MAX + 1)
134  {
135  PyErr_SetString(PyExc_SystemError, "python home path is too long");
136  return;
137  }
138  Py_SetPythonHome(wide);
139 #else
140  rtl_string_acquire(o.pData); // increase reference count
141  Py_SetPythonHome(o.pData->buffer);
142 #endif
143 }
144 
145 static void prependPythonPath( const OUString & pythonPathBootstrap )
146 {
147  OUStringBuffer bufPYTHONPATH( 256 );
148  sal_Int32 nIndex = 0;
149  while( true )
150  {
151  sal_Int32 nNew = pythonPathBootstrap.indexOf( ' ', nIndex );
152  OUString fileUrl;
153  if( nNew == -1 )
154  {
155  fileUrl = pythonPathBootstrap.copy(nIndex);
156  }
157  else
158  {
159  fileUrl = pythonPathBootstrap.copy(nIndex, nNew - nIndex);
160  }
161  OUString systemPath;
162  osl_getSystemPathFromFileURL( fileUrl.pData, &(systemPath.pData) );
163  bufPYTHONPATH.append( systemPath );
164  bufPYTHONPATH.append( static_cast<sal_Unicode>(SAL_PATHSEPARATOR) );
165  if( nNew == -1 )
166  break;
167  nIndex = nNew + 1;
168  }
169  const char * oldEnv = getenv( "PYTHONPATH");
170  if( oldEnv )
171  bufPYTHONPATH.append( OUString(oldEnv, strlen(oldEnv), osl_getThreadTextEncoding()) );
172 
173  OUString envVar("PYTHONPATH");
174  OUString envValue(bufPYTHONPATH.makeStringAndClear());
175  osl_setEnvironment(envVar.pData, envValue.pData);
176 }
177 
178 namespace {
179 
180 struct PythonInit
181 {
182 PythonInit() {
183  if (! Py_IsInitialized()) // may be inited by getComponentContext() already
184  {
185  OUString pythonPath;
186  OUString pythonHome;
187  OUString path( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("pythonloader.uno" ));
188  rtl::Bootstrap::expandMacros(path); //TODO: detect failure
189  rtl::Bootstrap bootstrap(path);
190 
191  // look for pythonhome
192  bootstrap.getFrom( "PYUNO_LOADER_PYTHONHOME", pythonHome );
193  bootstrap.getFrom( "PYUNO_LOADER_PYTHONPATH", pythonPath );
194 
195  // pythonhome+pythonpath must be set before Py_Initialize(), otherwise there appear warning on the console
196  // sadly, there is no api for setting the pythonpath, we have to use the environment variable
197  if( !pythonHome.isEmpty() )
198  setPythonHome( pythonHome );
199 
200  if( !pythonPath.isEmpty() )
201  prependPythonPath( pythonPath );
202 
203 #ifdef _WIN32
204  //extend PATH under windows to include the branddir/program so ssl libs will be found
205  //for use by terminal mailmerge dependency _ssl.pyd
206  OUString sEnvName("PATH");
207  OUString sPath;
208  osl_getEnvironment(sEnvName.pData, &sPath.pData);
209  OUString sBrandLocation("$BRAND_BASE_DIR/program");
210  rtl::Bootstrap::expandMacros(sBrandLocation);
211  osl::FileBase::getSystemPathFromFileURL(sBrandLocation, sBrandLocation);
212  sPath = OUStringBuffer(sPath).
213  append(static_cast<sal_Unicode>(SAL_PATHSEPARATOR)).
214  append(sBrandLocation).makeStringAndClear();
215  osl_setEnvironment(sEnvName.pData, sPath.pData);
216 #endif
217 
218 #if PY_MAJOR_VERSION >= 3
219  PyImport_AppendInittab( "pyuno", PyInit_pyuno );
220 #else
221  PyImport_AppendInittab( (char*)"pyuno", initpyuno );
222 #endif
223 
224 #if HAVE_FEATURE_READONLY_INSTALLSET
225  Py_DontWriteBytecodeFlag = 1;
226 #endif
227 
228  // initialize python
229  Py_Initialize();
230  PyEval_InitThreads();
231 
232  PyThreadState *tstate = PyThreadState_Get();
233  PyEval_ReleaseThread( tstate );
234  // This tstate is never used again, so delete it here.
235  // This prevents an assertion in PyThreadState_Swap on the
236  // PyThreadAttach below.
237  PyThreadState_Delete(tstate);
238  }
239 }
240 };
241 
242 }
243 
245 {
246  // tdf#114815 thread-safe static to init python only once
247  static PythonInit s_Init;
248 
250 
251  PyThreadAttach attach( PyInterpreterState_Head() );
252  {
253  // note: this can't race against getComponentContext() because
254  // either (in soffice.bin) CreateInstance() must be called before
255  // getComponentContext() can be called, or (in python.bin) the other
256  // way around
257  if( ! Runtime::isInitialized() )
258  {
259  Runtime::initialize( ctx );
260  }
261  Runtime runtime;
262 
263  PyRef pyCtx = runtime.any2PyObject(
264  css::uno::makeAny( ctx ) );
265 
266  PyRef clazz = getObjectFromLoaderModule( "Loader" );
267  PyRef args ( PyTuple_New( 1 ), SAL_NO_ACQUIRE, NOT_NULL );
268  PyTuple_SetItem( args.get(), 0 , pyCtx.getAcquired() );
269  PyRef pyInstance( PyObject_CallObject( clazz.get() , args.get() ), SAL_NO_ACQUIRE );
270  runtime.pyObject2Any( pyInstance ) >>= ret;
271  }
272  return ret;
273 }
274 
275 }
276 
277 
278 static const struct cppu::ImplementationEntry g_entries[] =
279 {
280  {
283  nullptr , 0
284  },
285  { nullptr, nullptr, nullptr, nullptr, nullptr, 0 }
286 };
287 
288 extern "C"
289 {
290 
291 SAL_DLLPUBLIC_EXPORT void * pythonloader_component_getFactory(
292  const char * pImplName, void * pServiceManager, void * pRegistryKey )
293 {
294  return cppu::component_getFactoryHelper( pImplName, pServiceManager, pRegistryKey , g_entries );
295 }
296 
297 }
298 
299 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */
css::uno::Reference< css::uno::XInterface >(SAL_CALL *ComponentFactoryFunc)(css CPPUHELPER_DLLPUBLIC css::uno::Reference< css::lang::XSingleComponentFactory > SAL_CALL createSingleComponentFactory(ComponentFactoryFunc fptr,::rtl::OUString const &rImplementationName, css::uno::Sequence< ::rtl::OUString > const &rServiceNames, rtl_ModuleCount *pModCount=NULL)
sal_Int32 nIndex
void * component_getFactoryHelper(char const *pImplName, SAL_UNUSED_PARAMETER void *, SAL_UNUSED_PARAMETER void *, const struct ImplementationEntry entries[])
#define SAL_PATHSEPARATOR
Helper class for keeping references to python objects.
Definition: pyuno.hxx:85
helper class for attaching the current thread to the python runtime.
Definition: pyuno.hxx:277
static void prependPythonPath(const OUString &pythonPathBootstrap)
tuple args
def bootstrap()
Definition: officehelper.py:37
PyObject * getAcquired() const
Definition: pyuno.hxx:107
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)
uno_Any a
static OUString getImplementationName()
static PyRef getObjectFromLoaderModule(const char *func)
static Sequence< OUString > getSupportedServiceNames()
static Reference< XInterface > CreateInstance(const Reference< XComponentContext > &ctx)
static void setPythonHome(const OUString &pythonHome)
static const struct cppu::ImplementationEntry g_entries[]
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:145
static PyRef getLoaderModule()
PyObject * get() const noexcept
Definition: pyuno.hxx:105
SAL_DLLPUBLIC_EXPORT void * pythonloader_component_getFactory(const char *pImplName, void *pServiceManager, void *pRegistryKey)
SAL_DLLPUBLIC_EXPORT void initpyuno(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:71
The pyuno::Runtime class keeps the internal state of the python UNO bridge for the currently in use p...
Definition: pyuno.hxx:169
PyRef any2PyObject(const css::uno::Any &source) const
converts something contained in a UNO Any to a Python object