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>
27
28#include <osl/process.h>
29#include <osl/file.hxx>
30#include <osl/thread.h>
31
32#include <rtl/ustrbuf.hxx>
33#include <rtl/bootstrap.hxx>
34
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
52using pyuno::PyRef;
53using pyuno::NOT_NULL;
54using pyuno::Runtime;
56
58using com::sun::star::uno::XInterface;
60using com::sun::star::uno::XComponentContext;
61using com::sun::star::uno::RuntimeException;
62
63namespace 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( "python-loader:" );
76 if( auto e = o3tl::tryAccess<css::uno::Exception>(a) )
77 buf.append( e->Message );
78 throw RuntimeException( buf.makeStringAndClear() );
79 }
80}
81
84{
86 PyImport_ImportModule( "pythonloader" ),
87 SAL_NO_ACQUIRE );
89 if( !module.is() )
90 {
91 throw RuntimeException( "pythonloader: Couldn't load pythonloader module" );
92 }
93 return PyRef( PyModule_GetDict( module.get() ));
94}
95
97static PyRef getObjectFromLoaderModule( const char * func )
98{
99 PyRef object( PyDict_GetItemString(getLoaderModule().get(), func ) );
100 if( !object.is() )
101 {
102 throw RuntimeException( "pythonloader: couldn't find core element pythonloader." +
103 OUString::createFromAscii( func ));
104 }
105 return object;
106}
107
108static void setPythonHome ( const OUString & pythonHome )
109{
110 OUString systemPythonHome;
111 osl_getSystemPathFromFileURL( pythonHome.pData, &(systemPythonHome.pData) );
112 // static because Py_SetPythonHome just copies the "wide" pointer
113 static wchar_t wide[PATH_MAX + 1];
114#if defined _WIN32
115 const size_t len = systemPythonHome.getLength();
116 if (len < std::size(wide))
117 wcsncpy(wide, o3tl::toW(systemPythonHome.getStr()), len + 1);
118#else
119 OString o = OUStringToOString(systemPythonHome, osl_getThreadTextEncoding());
120 size_t len = mbstowcs(wide, o.pData->buffer, PATH_MAX + 1);
121 if(len == size_t(-1))
122 {
123 PyErr_SetString(PyExc_SystemError, "invalid multibyte sequence in python home path");
124 return;
125 }
126#endif
127 if(len >= PATH_MAX + 1)
128 {
129 PyErr_SetString(PyExc_SystemError, "python home path is too long");
130 return;
131 }
133 Py_SetPythonHome(wide); // deprecated since python 3.11
135}
136
137static void prependPythonPath( std::u16string_view pythonPathBootstrap )
138{
139 OUStringBuffer bufPYTHONPATH( 256 );
140 bool bAppendSep = false;
141 sal_Int32 nIndex = 0;
142 while( true )
143 {
144 size_t nNew = pythonPathBootstrap.find( ' ', nIndex );
145 std::u16string_view fileUrl;
146 if( nNew == std::u16string_view::npos )
147 {
148 fileUrl = pythonPathBootstrap.substr(nIndex);
149 }
150 else
151 {
152 fileUrl = pythonPathBootstrap.substr(nIndex, nNew - nIndex);
153 }
154 OUString systemPath;
155 osl_getSystemPathFromFileURL( OUString(fileUrl).pData, &(systemPath.pData) );
156 if (!systemPath.isEmpty())
157 {
158 if (bAppendSep)
159 bufPYTHONPATH.append(static_cast<sal_Unicode>(SAL_PATHSEPARATOR));
160 bufPYTHONPATH.append(systemPath);
161 bAppendSep = true;
162 }
163 if( nNew == std::u16string_view::npos )
164 break;
165 nIndex = nNew + 1;
166 }
167 const char * oldEnv = getenv( "PYTHONPATH");
168 if( oldEnv )
169 {
170 if (bAppendSep)
171 bufPYTHONPATH.append( static_cast<sal_Unicode>(SAL_PATHSEPARATOR) );
172 bufPYTHONPATH.append( OUString(oldEnv, strlen(oldEnv), osl_getThreadTextEncoding()) );
173 }
174
175 OUString envVar("PYTHONPATH");
176 OUString envValue(bufPYTHONPATH.makeStringAndClear());
177 osl_setEnvironment(envVar.pData, envValue.pData);
178}
179
180namespace {
181
182void pythonInit() {
183 if ( Py_IsInitialized()) // may be inited by getComponentContext() already
184 return;
185
186 OUString pythonPath;
187 OUString pythonHome;
188 OUString path( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("pythonloader.uno" ));
189 rtl::Bootstrap::expandMacros(path); //TODO: detect failure
190 rtl::Bootstrap bootstrap(path);
191
192 // look for pythonhome
193 bootstrap.getFrom( "PYUNO_LOADER_PYTHONHOME", pythonHome );
194 bootstrap.getFrom( "PYUNO_LOADER_PYTHONPATH", pythonPath );
195
196 // pythonhome+pythonpath must be set before Py_Initialize(), otherwise there appear warning on the console
197 // sadly, there is no api for setting the pythonpath, we have to use the environment variable
198 if( !pythonHome.isEmpty() )
199 setPythonHome( pythonHome );
200
201 if( !pythonPath.isEmpty() )
202 prependPythonPath( pythonPath );
203
204#ifdef _WIN32
205 //extend PATH under windows to include the branddir/program so ssl libs will be found
206 //for use by terminal mailmerge dependency _ssl.pyd
207 OUString sEnvName("PATH");
208 OUString sPath;
209 osl_getEnvironment(sEnvName.pData, &sPath.pData);
210 OUString sBrandLocation("$BRAND_BASE_DIR/program");
211 rtl::Bootstrap::expandMacros(sBrandLocation);
212 osl::FileBase::getSystemPathFromFileURL(sBrandLocation, sBrandLocation);
213 sPath = sPath + OUStringChar(SAL_PATHSEPARATOR) + sBrandLocation;
214 osl_setEnvironment(sEnvName.pData, sPath.pData);
215#endif
216
217 PyImport_AppendInittab( "pyuno", PyInit_pyuno );
218
219#if HAVE_FEATURE_READONLY_INSTALLSET
220 Py_DontWriteBytecodeFlag = 1;
221#endif
222
223 // initialize python
224 Py_Initialize();
225#if PY_VERSION_HEX < 0x03090000
226 PyEval_InitThreads();
227#endif
228
229 PyThreadState *tstate = PyThreadState_Get();
230 PyEval_ReleaseThread( tstate );
231#if PY_VERSION_HEX < 0x030B0000
232 // This tstate is never used again, so delete it here.
233 // This prevents an assertion in PyThreadState_Swap on the
234 // PyThreadAttach below.
235 PyThreadState_Delete(tstate);
236#endif
237}
238
239}
240
241extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface*
243 css::uno::XComponentContext* ctx , css::uno::Sequence<css::uno::Any> const&)
244{
245 // tdf#114815 init python only once, via single-instace="true" in pythonloader.component
246 pythonInit();
247
249
250 PyThreadAttach attach( PyInterpreterState_Head() );
251 {
252 // note: this can't race against getComponentContext() because
253 // either (in soffice.bin) CreateInstance() must be called before
254 // getComponentContext() can be called, or (in python.bin) the other
255 // way around
256 if( ! Runtime::isInitialized() )
257 {
258 Runtime::initialize( ctx );
259 }
260 Runtime runtime;
261
262 PyRef pyCtx = runtime.any2PyObject(
263 css::uno::Any( css::uno::Reference(ctx) ) );
264
265 PyRef clazz = getObjectFromLoaderModule( "Loader" );
266 PyRef args ( PyTuple_New( 1 ), SAL_NO_ACQUIRE, NOT_NULL );
267 PyTuple_SetItem( args.get(), 0 , pyCtx.getAcquired() );
268 PyRef pyInstance( PyObject_CallObject( clazz.get() , args.get() ), SAL_NO_ACQUIRE );
269 runtime.pyObject2Any( pyInstance ) >>= ret;
270 }
271 ret->acquire();
272 return ret.get();
273}
274
275}
276
277/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Helper class for keeping references to python objects.
Definition: pyuno.hxx:80
PyObject * get() const noexcept
Definition: pyuno.hxx:99
PyObject * getAcquired() const
Definition: pyuno.hxx:101
helper class for attaching the current thread to the python runtime.
Definition: pyuno.hxx:272
The pyuno::Runtime class keeps the internal state of the python UNO bridge for the currently in use p...
Definition: pyuno.hxx:164
css::uno::Any extractUnoException(const PyRef &excType, const PyRef &excValue, const PyRef &excTraceback) const
extracts a proper uno exception from a given python exception
css::uno::Any pyObject2Any(const PyRef &source, enum ConversionMode mode=REJECT_UNO_ANY) const
converts a Python object to a UNO any
PyRef any2PyObject(const css::uno::Any &source) const
converts something contained in a UNO Any to a Python object
#define SAL_PATHSEPARATOR
#define SAL_CONFIGFILE(name)
sal_Int32 nIndex
uno_Any a
std::unique_ptr< sal_Int32[]> pData
ctx
def bootstrap()
Definition: officehelper.py:37
module
args
OString OUStringToOString(std::u16string_view str, ConnectionSettings const *settings)
static PyRef getLoaderModule()
static PyRef getObjectFromLoaderModule(const char *func)
static void prependPythonPath(std::u16string_view pythonPathBootstrap)
static void raiseRuntimeExceptionWhenNeeded()
SAL_DLLPUBLIC_EXPORT css::uno::XInterface * pyuno_Loader_get_implementation(css::uno::XComponentContext *ctx, css::uno::Sequence< css::uno::Any > const &)
static void setPythonHome(const OUString &pythonHome)
@ NOT_NULL
definition of a no acquire enum for ctors
Definition: pyuno.hxx:65
css::uno::Reference< css::linguistic2::XProofreadingIterator > get(css::uno::Reference< css::uno::XComponentContext > const &context)
SAL_DLLPUBLIC_EXPORT PyObject * PyInit_pyuno(void)
function called by the python runtime to initialize the pyuno module.
#define SAL_WNODEPRECATED_DECLARATIONS_POP
sal_uInt16 sal_Unicode
#define SAL_WNODEPRECATED_DECLARATIONS_PUSH