Skip to content

Commit

Permalink
Added python server plugins
Browse files Browse the repository at this point in the history
  • Loading branch information
elpaso committed Nov 26, 2014
1 parent 23af509 commit 98cab97
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 6 deletions.
8 changes: 8 additions & 0 deletions CMakeLists.txt
Expand Up @@ -46,6 +46,13 @@ IF(WITH_MAPSERVER)
SET (MAPSERVER_SKIP_ECW FALSE CACHE BOOL "Determines whether QGIS mapserver should disable ECW (ECW in server apps requires a special license)")
ENDIF(WITH_MAPSERVER)


SET (WITH_SERVER_PLUGINS FALSE CACHE BOOL "Determines whether QGIS mapserver support for python plugins should be built")
IF(WITH_SERVER_PLUGINS)
SET(MAPSERVER_HAVE_PYTHON_PLUGINS TRUE)
ENDIF(WITH_SERVER_PLUGINS)


# Custom widgets
SET (WITH_CUSTOM_WIDGETS FALSE CACHE BOOL "Determines whether QGIS custom widgets for Qt Designer should be built")

Expand Down Expand Up @@ -76,6 +83,7 @@ IF(WITH_ORACLE)
SET(HAVE_ORACLE TRUE)
ENDIF(WITH_ORACLE)


# try to configure and build python bindings by default
SET (WITH_BINDINGS TRUE CACHE BOOL "Determines whether python bindings should be built")
IF (WITH_BINDINGS)
Expand Down
27 changes: 24 additions & 3 deletions python/CMakeLists.txt
Expand Up @@ -22,7 +22,7 @@ SET (CMAKE_LIBRARY_OUTPUT_DIRECTORY ${QGIS_PYTHON_OUTPUT_DIRECTORY})
#
# NOTE: regular project 'make install' is unaffected

# Other target dependenciess will be added, per staged resource
# Other target dependencies will be added, per staged resource
ADD_CUSTOM_TARGET(staged-plugins)

# Plugins can also be staged with CMake option at build time
Expand Down Expand Up @@ -163,6 +163,27 @@ ENDIF(UNIX AND NOT SIP_VERSION_NUM LESS 265984)

ADD_SIP_PYTHON_MODULE(qgis._gui gui/gui.sip qgis_core qgis_gui)

SET(PY_MODULES core gui analysis networkanalysis)

# server module
IF (WITH_SERVER_PLUGINS)
INCLUDE_DIRECTORIES(
../src/mapserver
${CMAKE_BINARY_DIR}/src/mapserver
)

SET(PY_MODULES ${PY_MODULES} server)

FILE(GLOB sip_files_server
server/*.sip
)
SET(SIP_EXTRA_FILES_DEPEND ${sip_files_core} ${sip_files_server})
SET(SIP_EXTRA_OPTIONS ${PYQT4_SIP_FLAGS} -o -a ${CMAKE_BINARY_DIR}/python/qgis.server.api)
ADD_SIP_PYTHON_MODULE(qgis._server server/server.sip qgis_core qgis_server)

ENDIF (WITH_SERVER_PLUGINS)


# additional analysis includes
INCLUDE_DIRECTORIES(
../src/analysis/vector
Expand Down Expand Up @@ -234,7 +255,7 @@ ENDIF(WITH_CUSTOM_WIDGETS)
# Plugin utilities files to copy to staging or install
SET(PY_FILES
__init__.py
utils.py
utils.py
)

ADD_CUSTOM_TARGET(pyutils ALL)
Expand All @@ -251,7 +272,7 @@ FOREACH(pyfile ${PY_FILES})
PY_COMPILE(pyutils "${QGIS_PYTHON_OUTPUT_DIRECTORY}/${pyfile}")
ENDFOREACH(pyfile)

FOREACH(module core gui analysis networkanalysis)
FOREACH(module ${PY_MODULES})
ADD_CUSTOM_TARGET(py${module} ALL)
ADD_DEPENDENCIES(py${module} python_module_qgis__${module})
FILE(GLOB_RECURSE PY_FILES "${module}/*.py")
Expand Down
2 changes: 1 addition & 1 deletion src/CMakeLists.txt
Expand Up @@ -17,7 +17,7 @@ IF (WITH_BINDINGS)
ENDIF (WITH_BINDINGS)

IF (WITH_MAPSERVER)
ADD_SUBDIRECTORY(mapserver) # TODO: enable again once compilation is fixed
ADD_SUBDIRECTORY(mapserver)
ENDIF (WITH_MAPSERVER)

IF (WITH_CUSTOM_WIDGETS)
Expand Down
61 changes: 59 additions & 2 deletions src/mapserver/CMakeLists.txt
Expand Up @@ -54,14 +54,69 @@ SET (qgis_mapserv_MOC_HDRS
qgscapabilitiescache.h
qgsconfigcache.h
qgsmslayercache.h
qgsserverlogger.h
qgsserverlogger.h
)

SET (qgis_mapserv_RCCS
# not used
#qgis_mapserv.qrc
)



IF (WITH_SERVER_PLUGINS)
#############################################################
# qgis_server library


SET(qgis_mapserv_MOC_HDRS ${qgis_mapserv_MOC_HDRS}

)

SET(qgis_mapserv_SRCS ${qgis_mapserv_SRCS}
qgsserverplugins.cpp
qgsserverinterface.cpp
qgsserverfilter.cpp
qgsserverinterfaceimpl.cpp
)

QT4_WRAP_CPP(qgis_mapserv_MOC_SRCS ${qgis_mapserv_MOC_HDRS})

ADD_LIBRARY(qgis_server SHARED ${qgis_mapserv_SRCS} ${qgis_mapserv_MOC_SRCS} ${qgis_mapserv_HDRS} ${qgis_mapserv_MOC_HDRS} )

#generate unversioned libs for android
IF (NOT ANDROID)
SET_TARGET_PROPERTIES(qgis_server PROPERTIES
VERSION ${COMPLETE_VERSION}
SOVERSION ${COMPLETE_VERSION}
)
ENDIF (NOT ANDROID)

TARGET_LINK_LIBRARIES(qgis_server
qgis_core
qgis_analysis
qgispython
${PROJ_LIBRARY}
${FCGI_LIBRARY}
${POSTGRES_LIBRARY}
${GDAL_LIBRARY}
)

# install

INSTALL(TARGETS qgis_server
RUNTIME DESTINATION ${QGIS_BIN_DIR}
LIBRARY DESTINATION ${QGIS_LIB_DIR}
ARCHIVE DESTINATION ${QGIS_LIB_DIR}
FRAMEWORK DESTINATION ${QGIS_FW_SUBDIR}
PUBLIC_HEADER DESTINATION ${QGIS_INCLUDE_DIR})

INCLUDE_DIRECTORIES(
../python
)

ENDIF (WITH_SERVER_PLUGINS) # end qgis_server library

QT4_WRAP_UI (qgis_mapserv_UIS_H ${qgis_mapserv_UIS})

QT4_WRAP_CPP (qgis_mapserv_MOC_SRCS ${qgis_mapserv_MOC_HDRS})
Expand Down Expand Up @@ -95,7 +150,7 @@ INCLUDE_DIRECTORIES(
../core/composer
../core/layertree
../analysis/interpolation
../plugins/diagram_overlay
../plugins/diagram_overlay
.
)

Expand All @@ -106,12 +161,14 @@ ENDIF (WITH_INTERNAL_SPATIALITE)
TARGET_LINK_LIBRARIES(qgis_mapserv.fcgi
qgis_core
qgis_analysis
qgispython
${PROJ_LIBRARY}
${FCGI_LIBRARY}
${POSTGRES_LIBRARY}
${GDAL_LIBRARY}
)


########################################################
# Install

Expand Down
16 changes: 16 additions & 0 deletions src/python/qgspythonutils.h
Expand Up @@ -16,11 +16,19 @@
#ifndef QGSPYTHONUTILS_H
#define QGSPYTHONUTILS_H

// Needed for CMake variables defines
#include "qgsconfig.h"


#include <QString>
#include <QStringList>


class QgisInterface;
#ifdef MAPSERVER_HAVE_PYTHON_PLUGINS
class QgsServerInterface;
#endif


/**
All calls to Python functions in QGIS come here.
Expand All @@ -44,6 +52,14 @@ class PYTHON_EXPORT QgsPythonUtils
//! initialize python and import bindings
virtual void initPython( QgisInterface* interface ) = 0;

#ifdef MAPSERVER_HAVE_PYTHON_PLUGINS
//! initialize python and import server bindings
virtual void initServerPython( QgsServerInterface* interface ) = 0;

//! start server plugin: call plugin's classServerFactory(serverInterface) add to active plugins
virtual bool startServerPlugin( QString packageName ) = 0;
#endif

//! close python interpreter
virtual void exitPython() = 0;

Expand Down
152 changes: 152 additions & 0 deletions src/python/qgspythonutilsimpl.cpp
Expand Up @@ -180,6 +180,158 @@ void QgsPythonUtilsImpl::initPython( QgisInterface* interface )
_mainState = PyEval_SaveThread();
}


#ifdef MAPSERVER_HAVE_PYTHON_PLUGINS
void QgsPythonUtilsImpl::initServerPython( QgsServerInterface* interface)
{
// initialize python
Py_Initialize();

// initialize threading AND acquire GIL
PyEval_InitThreads();

mPythonEnabled = true;

mMainModule = PyImport_AddModule( "__main__" ); // borrowed reference
mMainDict = PyModule_GetDict( mMainModule ); // borrowed reference

runString( "import sys" ); // import sys module (for display / exception hooks)
runString( "import os" ); // import os module (for user paths)

// support for PYTHONSTARTUP-like environment variable: PYQGIS_STARTUP
// (unlike PYTHONHOME and PYTHONPATH, PYTHONSTARTUP is not supported for embedded interpreter by default)
// this is different than user's 'startup.py' (below), since it is loaded just after Py_Initialize
// it is very useful for cleaning sys.path, which may have undesireable paths, or for
// isolating/loading the initial environ without requiring a virt env, e.g. homebrew or MacPorts installs on Mac
runString( "pyqgstart = os.getenv('PYQGIS_STARTUP')\n" );
runString( "if pyqgstart is not None and os.path.exists(pyqgstart): execfile(pyqgstart)\n" );

#ifdef Q_OS_WIN
runString( "oldhome=None" );
runString( "if os.environ.has_key('HOME'): oldhome=os.environ['HOME']\n" );
runString( "os.environ['HOME']=os.environ['USERPROFILE']\n" );
#endif

// construct a list of plugin paths
// plugin dirs passed in QGIS_PLUGINPATH env. variable have highest priority (usually empty)
// locally installed plugins have priority over the system plugins
// use os.path.expanduser to support usernames with special characters (see #2512)
QStringList pluginpaths;
foreach ( QString p, extraPluginsPaths() )
{
if ( !QDir( p ).exists() )
{
QgsMessageOutput* msg = QgsMessageOutput::createMessageOutput();
msg->setTitle( QObject::tr( "Python error" ) );
msg->setMessage( QString( QObject::tr( "The extra plugin path '%1' does not exist !" ) ).arg( p ), QgsMessageOutput::MessageText );
msg->showMessage();
}
#ifdef Q_OS_WIN
p = p.replace( '\\', "\\\\" );
#endif
// we store here paths in unicode strings
// the str constant will contain utf8 code (through runString)
// so we call '...'.decode('utf-8') to make a unicode string
pluginpaths << '"' + p + "\".decode('utf-8')";
}
pluginpaths << homePluginsPath();
pluginpaths << '"' + pluginsPath() + '"';

// expect that bindings are installed locally, so add the path to modules
// also add path to plugins
QStringList newpaths;
newpaths << '"' + pythonPath() + '"';
newpaths << homePythonPath();
newpaths << pluginpaths;
runString( "sys.path = [" + newpaths.join( "," ) + "] + sys.path" );

// import SIP
if ( !runString( "import sip",
QObject::tr( "Couldn't load SIP module." ) + "\n" + QObject::tr( "Python support will be disabled." ) ) )
{
exitPython();
return;
}

// set PyQt4 api versions
QStringList apiV2classes;
apiV2classes << "QDate" << "QDateTime" << "QString" << "QTextStream" << "QTime" << "QUrl" << "QVariant";
foreach ( const QString& clsName, apiV2classes )
{
if ( !runString( QString( "sip.setapi('%1', 2)" ).arg( clsName ),
QObject::tr( "Couldn't set SIP API versions." ) + "\n" + QObject::tr( "Python support will be disabled." ) ) )
{
exitPython();
return;
}
}

// import Qt bindings
if ( !runString( "from PyQt4 import QtCore, QtGui",
QObject::tr( "Couldn't load PyQt4." ) + "\n" + QObject::tr( "Python support will be disabled." ) ) )
{
exitPython();
return;
}

// import QGIS bindings
QString error_msg = QObject::tr( "Couldn't load PyQGIS." ) + "\n" + QObject::tr( "Python support will be disabled." );
if ( !runString( "from qgis.core import *", error_msg ) || !runString( "from qgis.gui import *", error_msg ) )
{
exitPython();
return;
}

// This is the main difference with initInterface() for desktop plugins
// import QGIS Server bindings
error_msg = QObject::tr( "Couldn't load PyQGIS Server." ) + "\n" + QObject::tr( "Python support will be disabled." );
if ( !runString( "from qgis.server import *", error_msg ) )
{
exitPython();
return;
}


// import QGIS utils
error_msg = QObject::tr( "Couldn't load QGIS utils." ) + "\n" + QObject::tr( "Python support will be disabled." );
if ( !runString( "import qgis.utils", error_msg ) )
{
exitPython();
return;
}

// tell the utils script where to look for the plugins
runString( "qgis.utils.plugin_paths = [" + pluginpaths.join( "," ) + "]" );
runString( "qgis.utils.sys_plugin_path = \"" + pluginsPath() + "\"" );
runString( "qgis.utils.home_plugin_path = " + homePluginsPath() );

#ifdef Q_OS_WIN
runString( "if oldhome: os.environ['HOME']=oldhome\n" );
#endif

// This is the other main difference with initInterface() for desktop plugins
runString( "qgis.utils.initServerInterface(" + QString::number(( unsigned long ) interface ) + ")" );

QString startuppath = homePythonPath() + " + \"/startup.py\"";
runString( "if os.path.exists(" + startuppath + "): from startup import *\n" );

// release GIL!
// Later on, we acquire GIL just before doing some Python calls and
// release GIL again when the work with Python API is done.
// (i.e. there must be PyGILState_Ensure + PyGILState_Release pair
// around any calls to Python API, otherwise we may segfault!)
_mainState = PyEval_SaveThread();
}

bool QgsPythonUtilsImpl::startServerPlugin( QString packageName )
{
QString output;
evalString( "qgis.utils.startServerPlugin('" + packageName + "')", output );
return ( output == "True" );
}

#endif // End MAPSERVER_HAVE_PYTHON_PLUGINS

void QgsPythonUtilsImpl::exitPython()
{
Py_Finalize();
Expand Down
6 changes: 6 additions & 0 deletions src/python/qgspythonutilsimpl.h
Expand Up @@ -39,6 +39,12 @@ class QgsPythonUtilsImpl : public QgsPythonUtils
//! initialize python and import bindings
void initPython( QgisInterface* interface );

#ifdef MAPSERVER_HAVE_PYTHON_PLUGINS
//! initialize python for server and import bindings
void initServerPython( QgsServerInterface* interface );
bool startServerPlugin( QString packageName );
#endif

//! close python interpreter
void exitPython();

Expand Down

0 comments on commit 98cab97

Please sign in to comment.