Version 7.1.7.1, tag libreoffice-7.1.7.1
[LibreOffice.git] / pyuno / source / loader / pyuno_loader.cxx
blob873a3c3bbe71fb197c8620f9b3f1b0b9c97084db
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2 /*
3 * This file is part of the LibreOffice project.
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/.
9 * This file incorporates work covered by the following license notice:
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 .
20 #include <config_features.h>
21 #include <config_folders.h>
23 #include <pyuno.hxx>
25 #include <o3tl/any.hxx>
27 #include <osl/process.h>
28 #include <osl/file.hxx>
29 #include <osl/thread.h>
31 #include <rtl/ustrbuf.hxx>
32 #include <rtl/bootstrap.hxx>
34 #include <cppuhelper/factory.hxx>
36 #include <com/sun/star/uno/XComponentContext.hpp>
38 // apparently PATH_MAX is not standard and not defined by MSVC
39 #ifndef PATH_MAX
40 #ifdef _MAX_PATH
41 #define PATH_MAX _MAX_PATH
42 #else
43 #ifdef MAX_PATH
44 #define PATH_MAX MAX_PATH
45 #else
46 #error no PATH_MAX
47 #endif
48 #endif
49 #endif
51 using pyuno::PyRef;
52 using pyuno::NOT_NULL;
53 using pyuno::Runtime;
54 using pyuno::PyThreadAttach;
56 using com::sun::star::uno::Reference;
57 using com::sun::star::uno::XInterface;
58 using com::sun::star::uno::Sequence;
59 using com::sun::star::uno::XComponentContext;
60 using com::sun::star::uno::RuntimeException;
62 namespace pyuno_loader
65 /// @throws RuntimeException
66 static void raiseRuntimeExceptionWhenNeeded()
68 if( PyErr_Occurred() )
70 PyRef excType, excValue, excTraceback;
71 PyErr_Fetch(reinterpret_cast<PyObject **>(&excType), reinterpret_cast<PyObject**>(&excValue), reinterpret_cast<PyObject**>(&excTraceback));
72 Runtime runtime;
73 css::uno::Any a = runtime.extractUnoException( excType, excValue, excTraceback );
74 OUStringBuffer buf;
75 buf.append( "python-loader:" );
76 if( auto e = o3tl::tryAccess<css::uno::Exception>(a) )
77 buf.append( e->Message );
78 throw RuntimeException( buf.makeStringAndClear() );
82 /// @throws RuntimeException
83 static PyRef getLoaderModule()
85 PyRef module(
86 PyImport_ImportModule( "pythonloader" ),
87 SAL_NO_ACQUIRE );
88 raiseRuntimeExceptionWhenNeeded();
89 if( !module.is() )
91 throw RuntimeException( "pythonloader: Couldn't load pythonloader module" );
93 return PyRef( PyModule_GetDict( module.get() ));
96 /// @throws RuntimeException
97 static PyRef getObjectFromLoaderModule( const char * func )
99 PyRef object( PyDict_GetItemString(getLoaderModule().get(), func ) );
100 if( !object.is() )
102 throw RuntimeException( "pythonloader: couldn't find core element pythonloader." +
103 OUString::createFromAscii( func ));
105 return object;
108 static void setPythonHome ( const OUString & pythonHome )
110 OUString systemPythonHome;
111 osl_getSystemPathFromFileURL( pythonHome.pData, &(systemPythonHome.pData) );
112 OString o = OUStringToOString( systemPythonHome, osl_getThreadTextEncoding() );
113 // static because Py_SetPythonHome just copies the "wide" pointer
114 static wchar_t wide[PATH_MAX + 1];
115 size_t len = mbstowcs(wide, o.pData->buffer, PATH_MAX + 1);
116 if(len == size_t(-1))
118 PyErr_SetString(PyExc_SystemError, "invalid multibyte sequence in python home path");
119 return;
121 if(len == PATH_MAX + 1)
123 PyErr_SetString(PyExc_SystemError, "python home path is too long");
124 return;
126 Py_SetPythonHome(wide);
129 static void prependPythonPath( const OUString & pythonPathBootstrap )
131 OUStringBuffer bufPYTHONPATH( 256 );
132 bool bAppendSep = false;
133 sal_Int32 nIndex = 0;
134 while( true )
136 sal_Int32 nNew = pythonPathBootstrap.indexOf( ' ', nIndex );
137 OUString fileUrl;
138 if( nNew == -1 )
140 fileUrl = pythonPathBootstrap.copy(nIndex);
142 else
144 fileUrl = pythonPathBootstrap.copy(nIndex, nNew - nIndex);
146 OUString systemPath;
147 osl_getSystemPathFromFileURL( fileUrl.pData, &(systemPath.pData) );
148 if (!systemPath.isEmpty())
150 if (bAppendSep)
151 bufPYTHONPATH.append(static_cast<sal_Unicode>(SAL_PATHSEPARATOR));
152 bufPYTHONPATH.append(systemPath);
153 bAppendSep = true;
155 if( nNew == -1 )
156 break;
157 nIndex = nNew + 1;
159 const char * oldEnv = getenv( "PYTHONPATH");
160 if( oldEnv )
162 if (bAppendSep)
163 bufPYTHONPATH.append( static_cast<sal_Unicode>(SAL_PATHSEPARATOR) );
164 bufPYTHONPATH.append( OUString(oldEnv, strlen(oldEnv), osl_getThreadTextEncoding()) );
167 OUString envVar("PYTHONPATH");
168 OUString envValue(bufPYTHONPATH.makeStringAndClear());
169 osl_setEnvironment(envVar.pData, envValue.pData);
172 namespace {
174 void pythonInit() {
175 if ( Py_IsInitialized()) // may be inited by getComponentContext() already
176 return;
178 OUString pythonPath;
179 OUString pythonHome;
180 OUString path( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("pythonloader.uno" ));
181 rtl::Bootstrap::expandMacros(path); //TODO: detect failure
182 rtl::Bootstrap bootstrap(path);
184 // look for pythonhome
185 bootstrap.getFrom( "PYUNO_LOADER_PYTHONHOME", pythonHome );
186 bootstrap.getFrom( "PYUNO_LOADER_PYTHONPATH", pythonPath );
188 // pythonhome+pythonpath must be set before Py_Initialize(), otherwise there appear warning on the console
189 // sadly, there is no api for setting the pythonpath, we have to use the environment variable
190 if( !pythonHome.isEmpty() )
191 setPythonHome( pythonHome );
193 if( !pythonPath.isEmpty() )
194 prependPythonPath( pythonPath );
196 #ifdef _WIN32
197 //extend PATH under windows to include the branddir/program so ssl libs will be found
198 //for use by terminal mailmerge dependency _ssl.pyd
199 OUString sEnvName("PATH");
200 OUString sPath;
201 osl_getEnvironment(sEnvName.pData, &sPath.pData);
202 OUString sBrandLocation("$BRAND_BASE_DIR/program");
203 rtl::Bootstrap::expandMacros(sBrandLocation);
204 osl::FileBase::getSystemPathFromFileURL(sBrandLocation, sBrandLocation);
205 sPath = OUStringBuffer(sPath).
206 append(static_cast<sal_Unicode>(SAL_PATHSEPARATOR)).
207 append(sBrandLocation).makeStringAndClear();
208 osl_setEnvironment(sEnvName.pData, sPath.pData);
209 #endif
211 PyImport_AppendInittab( "pyuno", PyInit_pyuno );
213 #if HAVE_FEATURE_READONLY_INSTALLSET
214 Py_DontWriteBytecodeFlag = 1;
215 #endif
217 // initialize python
218 Py_Initialize();
219 #if PY_VERSION_HEX < 0x03090000
220 PyEval_InitThreads();
221 #endif
223 PyThreadState *tstate = PyThreadState_Get();
224 PyEval_ReleaseThread( tstate );
225 // This tstate is never used again, so delete it here.
226 // This prevents an assertion in PyThreadState_Swap on the
227 // PyThreadAttach below.
228 PyThreadState_Delete(tstate);
233 extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
234 pyuno_Loader_get_implementation(
235 css::uno::XComponentContext* ctx , css::uno::Sequence<css::uno::Any> const&)
237 // tdf#114815 init python only once, via single-instace="true" in pythonloader.component
238 pythonInit();
240 Reference< XInterface > ret;
242 PyThreadAttach attach( PyInterpreterState_Head() );
244 // note: this can't race against getComponentContext() because
245 // either (in soffice.bin) CreateInstance() must be called before
246 // getComponentContext() can be called, or (in python.bin) the other
247 // way around
248 if( ! Runtime::isInitialized() )
250 Runtime::initialize( ctx );
252 Runtime runtime;
254 PyRef pyCtx = runtime.any2PyObject(
255 css::uno::makeAny( css::uno::Reference(ctx) ) );
257 PyRef clazz = getObjectFromLoaderModule( "Loader" );
258 PyRef args ( PyTuple_New( 1 ), SAL_NO_ACQUIRE, NOT_NULL );
259 PyTuple_SetItem( args.get(), 0 , pyCtx.getAcquired() );
260 PyRef pyInstance( PyObject_CallObject( clazz.get() , args.get() ), SAL_NO_ACQUIRE );
261 runtime.pyObject2Any( pyInstance ) >>= ret;
263 ret->acquire();
264 return ret.get();
269 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */