1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
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>
25 #include <o3tl/any.hxx>
26 #include <o3tl/char16_t2wchar_t.hxx>
28 #include <osl/process.h>
29 #include <osl/file.hxx>
30 #include <osl/thread.h>
32 #include <rtl/ustrbuf.hxx>
33 #include <rtl/bootstrap.hxx>
35 #include <cppuhelper/factory.hxx>
37 #include <com/sun/star/uno/XComponentContext.hpp>
39 // apparently PATH_MAX is not standard and not defined by MSVC
42 #define PATH_MAX _MAX_PATH
45 #define PATH_MAX MAX_PATH
53 using pyuno::NOT_NULL
;
55 using pyuno::PyThreadAttach
;
57 using com::sun::star::uno::Reference
;
58 using com::sun::star::uno::XInterface
;
59 using com::sun::star::uno::Sequence
;
60 using com::sun::star::uno::XComponentContext
;
61 using com::sun::star::uno::RuntimeException
;
63 namespace pyuno_loader
66 /// @throws RuntimeException
67 static void raiseRuntimeExceptionWhenNeeded()
69 if( PyErr_Occurred() )
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
);
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() );
83 /// @throws RuntimeException
84 static PyRef
getLoaderModule()
87 PyImport_ImportModule( "pythonloader" ),
89 raiseRuntimeExceptionWhenNeeded();
92 throw RuntimeException( "pythonloader: Couldn't load pythonloader module" );
94 return PyRef( PyModule_GetDict( module
.get() ));
97 /// @throws RuntimeException
98 static PyRef
getObjectFromLoaderModule( const char * func
)
100 PyRef
object( PyDict_GetItemString(getLoaderModule().get(), func
) );
103 throw RuntimeException( "pythonloader: couldn't find core element pythonloader." +
104 OUString::createFromAscii( func
));
109 static void setPythonHome ( const OUString
& pythonHome
)
111 OUString systemPythonHome
;
112 osl_getSystemPathFromFileURL( pythonHome
.pData
, &(systemPythonHome
.pData
) );
113 // static because Py_SetPythonHome just copies the "wide" pointer
114 static wchar_t wide
[PATH_MAX
+ 1];
116 const size_t len
= systemPythonHome
.getLength();
117 if (len
< std::size(wide
))
118 wcsncpy(wide
, o3tl::toW(systemPythonHome
.getStr()), len
+ 1);
120 OString o
= OUStringToOString(systemPythonHome
, osl_getThreadTextEncoding());
121 size_t len
= mbstowcs(wide
, o
.pData
->buffer
, PATH_MAX
+ 1);
122 if(len
== size_t(-1))
124 PyErr_SetString(PyExc_SystemError
, "invalid multibyte sequence in python home path");
128 if(len
>= PATH_MAX
+ 1)
130 PyErr_SetString(PyExc_SystemError
, "python home path is too long");
133 Py_SetPythonHome(wide
);
136 static void prependPythonPath( const OUString
& pythonPathBootstrap
)
138 OUStringBuffer
bufPYTHONPATH( 256 );
139 bool bAppendSep
= false;
140 sal_Int32 nIndex
= 0;
143 sal_Int32 nNew
= pythonPathBootstrap
.indexOf( ' ', nIndex
);
147 fileUrl
= pythonPathBootstrap
.copy(nIndex
);
151 fileUrl
= pythonPathBootstrap
.copy(nIndex
, nNew
- nIndex
);
154 osl_getSystemPathFromFileURL( fileUrl
.pData
, &(systemPath
.pData
) );
155 if (!systemPath
.isEmpty())
158 bufPYTHONPATH
.append(static_cast<sal_Unicode
>(SAL_PATHSEPARATOR
));
159 bufPYTHONPATH
.append(systemPath
);
166 const char * oldEnv
= getenv( "PYTHONPATH");
170 bufPYTHONPATH
.append( static_cast<sal_Unicode
>(SAL_PATHSEPARATOR
) );
171 bufPYTHONPATH
.append( OUString(oldEnv
, strlen(oldEnv
), osl_getThreadTextEncoding()) );
174 OUString
envVar("PYTHONPATH");
175 OUString
envValue(bufPYTHONPATH
.makeStringAndClear());
176 osl_setEnvironment(envVar
.pData
, envValue
.pData
);
182 if ( Py_IsInitialized()) // may be inited by getComponentContext() already
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
);
191 // look for pythonhome
192 bootstrap
.getFrom( "PYUNO_LOADER_PYTHONHOME", pythonHome
);
193 bootstrap
.getFrom( "PYUNO_LOADER_PYTHONPATH", pythonPath
);
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
);
200 if( !pythonPath
.isEmpty() )
201 prependPythonPath( pythonPath
);
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");
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
);
218 PyImport_AppendInittab( "pyuno", PyInit_pyuno
);
220 #if HAVE_FEATURE_READONLY_INSTALLSET
221 Py_DontWriteBytecodeFlag
= 1;
226 #if PY_VERSION_HEX < 0x03090000
227 PyEval_InitThreads();
230 PyThreadState
*tstate
= PyThreadState_Get();
231 PyEval_ReleaseThread( tstate
);
232 #if PY_VERSION_HEX < 0x030B0000
233 // This tstate is never used again, so delete it here.
234 // This prevents an assertion in PyThreadState_Swap on the
235 // PyThreadAttach below.
236 PyThreadState_Delete(tstate
);
242 extern "C" SAL_DLLPUBLIC_EXPORT
css::uno::XInterface
*
243 pyuno_Loader_get_implementation(
244 css::uno::XComponentContext
* ctx
, css::uno::Sequence
<css::uno::Any
> const&)
246 // tdf#114815 init python only once, via single-instace="true" in pythonloader.component
249 Reference
< XInterface
> ret
;
251 PyThreadAttach
attach( PyInterpreterState_Head() );
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
257 if( ! Runtime::isInitialized() )
259 Runtime::initialize( ctx
);
263 PyRef pyCtx
= runtime
.any2PyObject(
264 css::uno::makeAny( css::uno::Reference(ctx
) ) );
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
;
278 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */