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>
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
41 #define PATH_MAX _MAX_PATH
44 #define PATH_MAX MAX_PATH
52 using pyuno::NOT_NULL
;
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
));
73 css::uno::Any a
= runtime
.extractUnoException( excType
, excValue
, excTraceback
);
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()
86 PyImport_ImportModule( "pythonloader" ),
88 raiseRuntimeExceptionWhenNeeded();
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
) );
102 throw RuntimeException( "pythonloader: couldn't find core element pythonloader." +
103 OUString::createFromAscii( func
));
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");
121 if(len
== PATH_MAX
+ 1)
123 PyErr_SetString(PyExc_SystemError
, "python home path is too long");
126 Py_SetPythonHome(wide
);
129 static void prependPythonPath( const OUString
& pythonPathBootstrap
)
131 OUStringBuffer
bufPYTHONPATH( 256 );
132 bool bAppendSep
= false;
133 sal_Int32 nIndex
= 0;
136 sal_Int32 nNew
= pythonPathBootstrap
.indexOf( ' ', nIndex
);
140 fileUrl
= pythonPathBootstrap
.copy(nIndex
);
144 fileUrl
= pythonPathBootstrap
.copy(nIndex
, nNew
- nIndex
);
147 osl_getSystemPathFromFileURL( fileUrl
.pData
, &(systemPath
.pData
) );
148 if (!systemPath
.isEmpty())
151 bufPYTHONPATH
.append(static_cast<sal_Unicode
>(SAL_PATHSEPARATOR
));
152 bufPYTHONPATH
.append(systemPath
);
159 const char * oldEnv
= getenv( "PYTHONPATH");
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
);
175 if ( Py_IsInitialized()) // may be inited by getComponentContext() already
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
);
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");
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
);
211 PyImport_AppendInittab( "pyuno", PyInit_pyuno
);
213 #if HAVE_FEATURE_READONLY_INSTALLSET
214 Py_DontWriteBytecodeFlag
= 1;
219 #if PY_VERSION_HEX < 0x03090000
220 PyEval_InitThreads();
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
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
248 if( ! Runtime::isInitialized() )
250 Runtime::initialize( ctx
);
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
;
269 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */