/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include #include #include #include #include #include #include #include #include // apparently PATH_MAX is not standard and not defined by MSVC #ifndef PATH_MAX #ifdef _MAX_PATH #define PATH_MAX _MAX_PATH #else #ifdef MAX_PATH #define PATH_MAX MAX_PATH #else #error no PATH_MAX #endif #endif #endif using pyuno::PyRef; using pyuno::NOT_NULL; using pyuno::Runtime; using pyuno::PyThreadAttach; using com::sun::star::uno::Reference; using com::sun::star::uno::XInterface; using com::sun::star::uno::Sequence; using com::sun::star::uno::XComponentContext; using com::sun::star::uno::RuntimeException; namespace pyuno_loader { /// @throws RuntimeException static void raiseRuntimeExceptionWhenNeeded() { if( PyErr_Occurred() ) { PyRef excType, excValue, excTraceback; PyErr_Fetch(reinterpret_cast(&excType), reinterpret_cast(&excValue), reinterpret_cast(&excTraceback)); Runtime runtime; css::uno::Any a = runtime.extractUnoException( excType, excValue, excTraceback ); OUStringBuffer buf; buf.append( "python-loader:" ); if( auto e = o3tl::tryAccess(a) ) buf.append( e->Message ); throw RuntimeException( buf.makeStringAndClear() ); } } /// @throws RuntimeException static PyRef getLoaderModule() { PyRef module( PyImport_ImportModule( "pythonloader" ), SAL_NO_ACQUIRE ); raiseRuntimeExceptionWhenNeeded(); if( !module.is() ) { throw RuntimeException( "pythonloader: Couldn't load pythonloader module" ); } return PyRef( PyModule_GetDict( module.get() )); } /// @throws RuntimeException static PyRef getObjectFromLoaderModule( const char * func ) { PyRef object( PyDict_GetItemString(getLoaderModule().get(), func ) ); if( !object.is() ) { throw RuntimeException( "pythonloader: couldn't find core element pythonloader." + OUString::createFromAscii( func )); } return object; } static void setPythonHome ( const OUString & pythonHome ) { OUString systemPythonHome; osl_getSystemPathFromFileURL( pythonHome.pData, &(systemPythonHome.pData) ); // static because Py_SetPythonHome just copies the "wide" pointer static wchar_t wide[PATH_MAX + 1]; #if defined _WIN32 const size_t len = systemPythonHome.getLength(); if (len < std::size(wide)) wcsncpy(wide, o3tl::toW(systemPythonHome.getStr()), len + 1); #else OString o = OUStringToOString(systemPythonHome, osl_getThreadTextEncoding()); size_t len = mbstowcs(wide, o.pData->buffer, PATH_MAX + 1); #endif if(len == size_t(-1)) { PyErr_SetString(PyExc_SystemError, "invalid multibyte sequence in python home path"); return; } if(len >= PATH_MAX + 1) { PyErr_SetString(PyExc_SystemError, "python home path is too long"); return; } Py_SetPythonHome(wide); } static void prependPythonPath( const OUString & pythonPathBootstrap ) { OUStringBuffer bufPYTHONPATH( 256 ); bool bAppendSep = false; sal_Int32 nIndex = 0; while( true ) { sal_Int32 nNew = pythonPathBootstrap.indexOf( ' ', nIndex ); OUString fileUrl; if( nNew == -1 ) { fileUrl = pythonPathBootstrap.copy(nIndex); } else { fileUrl = pythonPathBootstrap.copy(nIndex, nNew - nIndex); } OUString systemPath; osl_getSystemPathFromFileURL( fileUrl.pData, &(systemPath.pData) ); if (!systemPath.isEmpty()) { if (bAppendSep) bufPYTHONPATH.append(static_cast(SAL_PATHSEPARATOR)); bufPYTHONPATH.append(systemPath); bAppendSep = true; } if( nNew == -1 ) break; nIndex = nNew + 1; } const char * oldEnv = getenv( "PYTHONPATH"); if( oldEnv ) { if (bAppendSep) bufPYTHONPATH.append( static_cast(SAL_PATHSEPARATOR) ); bufPYTHONPATH.append( OUString(oldEnv, strlen(oldEnv), osl_getThreadTextEncoding()) ); } OUString envVar("PYTHONPATH"); OUString envValue(bufPYTHONPATH.makeStringAndClear()); osl_setEnvironment(envVar.pData, envValue.pData); } namespace { void pythonInit() { if ( Py_IsInitialized()) // may be inited by getComponentContext() already return; OUString pythonPath; OUString pythonHome; OUString path( "$BRAND_BASE_DIR/" LIBO_ETC_FOLDER "/" SAL_CONFIGFILE("pythonloader.uno" )); rtl::Bootstrap::expandMacros(path); //TODO: detect failure rtl::Bootstrap bootstrap(path); // look for pythonhome bootstrap.getFrom( "PYUNO_LOADER_PYTHONHOME", pythonHome ); bootstrap.getFrom( "PYUNO_LOADER_PYTHONPATH", pythonPath ); // pythonhome+pythonpath must be set before Py_Initialize(), otherwise there appear warning on the console // sadly, there is no api for setting the pythonpath, we have to use the environment variable if( !pythonHome.isEmpty() ) setPythonHome( pythonHome ); if( !pythonPath.isEmpty() ) prependPythonPath( pythonPath ); #ifdef _WIN32 //extend PATH under windows to include the branddir/program so ssl libs will be found //for use by terminal mailmerge dependency _ssl.pyd OUString sEnvName("PATH"); OUString sPath; osl_getEnvironment(sEnvName.pData, &sPath.pData); OUString sBrandLocation("$BRAND_BASE_DIR/program"); rtl::Bootstrap::expandMacros(sBrandLocation); osl::FileBase::getSystemPathFromFileURL(sBrandLocation, sBrandLocation); sPath = OUStringBuffer(sPath). append(static_cast(SAL_PATHSEPARATOR)). append(sBrandLocation).makeStringAndClear(); osl_setEnvironment(sEnvName.pData, sPath.pData); #endif PyImport_AppendInittab( "pyuno", PyInit_pyuno ); #if HAVE_FEATURE_READONLY_INSTALLSET Py_DontWriteBytecodeFlag = 1; #endif // initialize python Py_Initialize(); PyEval_InitThreads(); PyThreadState *tstate = PyThreadState_Get(); PyEval_ReleaseThread( tstate ); // This tstate is never used again, so delete it here. // This prevents an assertion in PyThreadState_Swap on the // PyThreadAttach below. PyThreadState_Delete(tstate); } } extern "C" SAL_DLLPUBLIC_EXPORT css::uno::XInterface* pyuno_Loader_get_implementation( css::uno::XComponentContext* ctx , css::uno::Sequence const&) { // tdf#114815 init python only once, via single-instace="true" in pythonloader.component pythonInit(); Reference< XInterface > ret; PyThreadAttach attach( PyInterpreterState_Head() ); { // note: this can't race against getComponentContext() because // either (in soffice.bin) CreateInstance() must be called before // getComponentContext() can be called, or (in python.bin) the other // way around if( ! Runtime::isInitialized() ) { Runtime::initialize( ctx ); } Runtime runtime; PyRef pyCtx = runtime.any2PyObject( css::uno::makeAny( css::uno::Reference(ctx) ) ); PyRef clazz = getObjectFromLoaderModule( "Loader" ); PyRef args ( PyTuple_New( 1 ), SAL_NO_ACQUIRE, NOT_NULL ); PyTuple_SetItem( args.get(), 0 , pyCtx.getAcquired() ); PyRef pyInstance( PyObject_CallObject( clazz.get() , args.get() ), SAL_NO_ACQUIRE ); runtime.pyObject2Any( pyInstance ) >>= ret; } ret->acquire(); return ret.get(); } } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */