diff --git a/src/app/main.cpp b/src/app/main.cpp index 2d8336547d61..aab5a7f46c09 100644 --- a/src/app/main.cpp +++ b/src/app/main.cpp @@ -443,208 +443,105 @@ int main( int argc, char *argv[] ) //put all QGIS settings in the same place configpath = QgsApplication::qgisSettingsDirPath(); QgsDebugMsg( QString( "Android: configpath set to %1" ).arg( configpath ) ); -#elif defined(Q_WS_WIN) - for ( int i = 1; i < argc; i++ ) - { - QString arg = argv[i]; +#endif - if ( arg == "--help" || arg == "-?" ) - { - usage( argv[0] ); - return 2; - } - else if ( arg == "-nologo" || arg == "-n" ) - { - myHideSplash = true; - } - else if ( arg == "--noplugins" || arg == "-P" ) - { - myRestorePlugins = false; - } - else if ( arg == "--nocustomization" || arg == "-C" ) - { - myCustomization = false; - } - else if ( i + 1 < argc && ( arg == "--snapshot" || arg == "-s" ) ) - { - mySnapshotFileName = QDir::convertSeparators( QFileInfo( QFile::decodeName( argv[++i] ) ).absoluteFilePath() ); - } - else if ( i + 1 < argc && ( arg == "--width" || arg == "-w" ) ) - { - mySnapshotWidth = QString( argv[++i] ).toInt(); - } - else if ( i + 1 < argc && ( arg == "--height" || arg == "-h" ) ) - { - mySnapshotHeight = QString( argv[++i] ).toInt(); - } - else if ( i + 1 < argc && ( arg == "--lang" || arg == "-l" ) ) - { - myTranslationCode = argv[++i]; - } - else if ( i + 1 < argc && ( arg == "--project" || arg == "-p" ) ) - { - myProjectFileName = QDir::convertSeparators( QFileInfo( QFile::decodeName( argv[++i] ) ).absoluteFilePath() ); - } - else if ( i + 1 < argc && ( arg == "--extent" || arg == "-e" ) ) - { - myInitialExtent = argv[++i]; - } - else if ( i + 1 < argc && ( arg == "--optionspath" || arg == "-o" ) ) - { - optionpath = argv[++i]; - } - else if ( i + 1 < argc && ( arg == "--configpath" || arg == "-c" ) ) - { - configpath = argv[++i]; - } - else if ( i + 1 < argc && ( arg == "--code" || arg == "-f" ) ) - { - pythonfile = argv[++i]; - } - else if ( i + 1 < argc && ( arg == "--customizationfile" || arg == "-z" ) ) - { - customizationfile = argv[++i]; - } - else - { - myFileList.append( QDir::convertSeparators( QFileInfo( QFile::decodeName( argv[i] ) ).absoluteFilePath() ) ); - } + QStringList args; + { + // Build a local QCoreApplication from arguments. This way, arguments are correctly parsed from their native locale + // It will use QString::fromLocal8Bit( argv ) under Unix and GetCommandLine() under Windows. + QCoreApplication coreApp( argc, argv ); + args = QCoreApplication::arguments(); } -#else + if ( !bundleclicked( argc, argv ) ) { - - //////////////////////////////////////////////////////////////// - // Use the GNU Getopts utility to parse cli arguments - // Invokes ctor `GetOpt (int argc, char **argv, char *optstring);' - /////////////////////////////////////////////////////////////// - int optionChar; - while ( 1 ) + for ( int i = 1; i < args.size(); ++i ) { - static struct option long_options[] = + QString arg = args[i]; + + if ( arg == "--help" || arg == "-?" ) { - /* These options set a flag. */ - {"help", no_argument, 0, '?'}, - {"nologo", no_argument, 0, 'n'}, - {"noplugins", no_argument, 0, 'P'}, - {"nocustomization", no_argument, 0, 'C'}, - /* These options don't set a flag. - * We distinguish them by their indices. */ - {"snapshot", required_argument, 0, 's'}, - {"width", required_argument, 0, 'w'}, - {"height", required_argument, 0, 'h'}, - {"lang", required_argument, 0, 'l'}, - {"project", required_argument, 0, 'p'}, - {"extent", required_argument, 0, 'e'}, - {"optionspath", required_argument, 0, 'o'}, - {"configpath", required_argument, 0, 'c'}, - {"customizationfile", required_argument, 0, 'z'}, - {"code", required_argument, 0, 'f'}, - {"android", required_argument, 0, 'a'}, - {0, 0, 0, 0} - }; - - /* getopt_long stores the option index here. */ - int option_index = 0; - - optionChar = getopt_long( argc, argv, "swhlpeoc", - long_options, &option_index ); - QgsDebugMsg( QString( "Qgis main Debug" ) + optionChar ); - /* Detect the end of the options. */ - if ( optionChar == -1 ) - break; - - switch ( optionChar ) + usage( args[0].toStdString() ); + return 2; + } + else if ( arg == "--nologo" || arg == "-n" ) + { + myHideSplash = true; + } + else if ( arg == "--noplugins" || arg == "-P" ) + { + myRestorePlugins = false; + } + else if ( arg == "--nocustomization" || arg == "-C" ) + { + myCustomization = false; + } + else if ( i + 1 < argc && ( arg == "--snapshot" || arg == "-s" ) ) + { + mySnapshotFileName = QDir::convertSeparators( QFileInfo( args[++i] ).absoluteFilePath() ); + } + else if ( i + 1 < argc && ( arg == "--width" || arg == "-w" ) ) + { + mySnapshotWidth = QString( args[++i] ).toInt(); + } + else if ( i + 1 < argc && ( arg == "--height" || arg == "-h" ) ) + { + mySnapshotHeight = QString( args[++i] ).toInt(); + } + else if ( i + 1 < argc && ( arg == "--lang" || arg == "-l" ) ) + { + myTranslationCode = args[++i]; + } + else if ( i + 1 < argc && ( arg == "--project" || arg == "-p" ) ) + { + myProjectFileName = QDir::convertSeparators( QFileInfo( args[++i] ).absoluteFilePath() ); + } + else if ( i + 1 < argc && ( arg == "--extent" || arg == "-e" ) ) + { + myInitialExtent = args[++i]; + } + else if ( i + 1 < argc && ( arg == "--optionspath" || arg == "-o" ) ) + { + optionpath = QDir::convertSeparators( QDir( args[++i] ).absolutePath() ); + } + else if ( i + 1 < argc && ( arg == "--configpath" || arg == "-c" ) ) + { + configpath = QDir::convertSeparators( QDir( args[++i] ).absolutePath() ); + } + else if ( i + 1 < argc && ( arg == "--code" || arg == "-f" ) ) + { + pythonfile = QDir::convertSeparators( QFileInfo( args[++i] ).absoluteFilePath() ); + } + else if ( i + 1 < argc && ( arg == "--customizationfile" || arg == "-z" ) ) { - case 0: - /* If this option set a flag, do nothing else now. */ - if ( long_options[option_index].flag != 0 ) - break; - printf( "option %s", long_options[option_index].name ); - if ( optarg ) - printf( " with arg %s", optarg ); - printf( "\n" ); - break; - - case 's': - mySnapshotFileName = QDir::convertSeparators( QFileInfo( QFile::decodeName( optarg ) ).absoluteFilePath() ); - break; - - case 'w': - mySnapshotWidth = QString( optarg ).toInt(); - break; - - case 'h': - mySnapshotHeight = QString( optarg ).toInt(); - break; - - case 'n': - myHideSplash = true; - break; - - case 'l': - myTranslationCode = optarg; - break; - - case 'p': - myProjectFileName = QDir::convertSeparators( QFileInfo( QFile::decodeName( optarg ) ).absoluteFilePath() ); - break; - - case 'P': - myRestorePlugins = false; - break; - - case 'C': - myCustomization = false; - break; - - case 'e': - myInitialExtent = optarg; - break; - - case 'o': - optionpath = optarg; - break; - - case 'c': - configpath = optarg; - break; - - case 'f': - pythonfile = optarg; - break; - - case 'z': - customizationfile = optarg; - break; - - case '?': - usage( argv[0] ); - return 2; // XXX need standard exit codes - break; - - default: - QgsDebugMsg( QString( "%1: getopt returned character code %2" ).arg( argv[0] ).arg( optionChar ) ); - return 1; // XXX need standard exit codes + customizationfile = QDir::convertSeparators( QFileInfo( args[++i] ).absoluteFilePath() ); + } + else + { + myFileList.append( QDir::convertSeparators( QFileInfo( args[i] ).absoluteFilePath() ) ); } } + } - // Add any remaining args to the file list - we will attempt to load them - // as layers in the map view further down.... - QgsDebugMsg( QString( "Files specified on command line: %1" ).arg( optind ) ); - if ( optind < argc ) + ///////////////////////////////////////////////////////////////////// + // If no --project was specified, parse the args to look for a // + // .qgs file and set myProjectFileName to it. This allows loading // + // of a project file by clicking on it in various desktop managers // + // where an appropriate mime-type has been set up. // + ///////////////////////////////////////////////////////////////////// + if ( myProjectFileName.isEmpty() ) + { + // check for a .qgs + for ( int i = 0; i < args.size(); i++ ) { - while ( optind < argc ) + QString arg = QDir::convertSeparators( QFileInfo( args[i] ).absoluteFilePath() ); + if ( arg.contains( ".qgs" ) ) { -#ifdef QGISDEBUG - int idx = optind; - QgsDebugMsg( QString( "%1: %2" ).arg( idx ).arg( argv[idx] ) ); -#endif - myFileList.append( QDir::convertSeparators( QFileInfo( QFile::decodeName( argv[optind++] ) ).absoluteFilePath() ) ); + myProjectFileName = arg; + break; } } } -#endif ///////////////////////////////////////////////////////////////////// @@ -956,26 +853,6 @@ int main( int argc, char *argv[] ) QgsCustomization::instance(), SLOT( preNotify( QObject *, QEvent *, bool * ) ) ); - ///////////////////////////////////////////////////////////////////// - // If no --project was specified, parse the args to look for a // - // .qgs file and set myProjectFileName to it. This allows loading // - // of a project file by clicking on it in various desktop managers // - // where an appropriate mime-type has been set up. // - ///////////////////////////////////////////////////////////////////// - if ( myProjectFileName.isEmpty() ) - { - // check for a .qgs - for ( int i = 0; i < argc; i++ ) - { - QString arg = QDir::convertSeparators( QFileInfo( QFile::decodeName( argv[i] ) ).absoluteFilePath() ); - if ( arg.contains( ".qgs" ) ) - { - myProjectFileName = arg; - break; - } - } - } - ///////////////////////////////////////////////////////////////////// // Load a project file if one was specified ///////////////////////////////////////////////////////////////////// diff --git a/src/python/qgspythonutilsimpl.cpp b/src/python/qgspythonutilsimpl.cpp index 393471c3d7be..b4ccba4f8be8 100644 --- a/src/python/qgspythonutilsimpl.cpp +++ b/src/python/qgspythonutilsimpl.cpp @@ -77,7 +77,17 @@ void QgsPythonUtilsImpl::initPython( QgisInterface* interface ) #ifdef Q_OS_WIN p = p.replace( '\\', "\\\\" ); #endif - pluginpaths << '"' + p + '"'; + 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(); + } + // 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() + '"'; @@ -469,11 +479,11 @@ QString QgsPythonUtilsImpl::homePythonPath() QString settingsDir = QgsApplication::qgisSettingsDirPath(); if ( QDir::cleanPath( settingsDir ) == QDir::homePath() + QString( "/.qgis%1" ).arg( 2 /* FIXME QGis::QGIS_VERSION_INT / 10000 */ ) ) { - return QString( "os.path.expanduser(\"~/.qgis%1/python\")" ).arg( 2 /* FIXME: QGis::QGIS_VERSION_INT / 10000 */ ); + return QString( "os.path.expanduser(\"~/.qgis%1/python\").decode('utf-8')" ).arg( 2 /* FIXME: QGis::QGIS_VERSION_INT / 10000 */ ); } else { - return '"' + settingsDir.replace( '\\', "\\\\" ) + "python\""; + return '"' + settingsDir.replace( '\\', "\\\\" ) + "python\".decode('utf-8')"; } } diff --git a/tests/src/python/CMakeLists.txt b/tests/src/python/CMakeLists.txt index 1c545bd07419..2a30e7ced784 100644 --- a/tests/src/python/CMakeLists.txt +++ b/tests/src/python/CMakeLists.txt @@ -29,4 +29,4 @@ ADD_PYTHON_TEST(PyQgsPalLabelingServer test_qgspallabeling_server.py) ADD_PYTHON_TEST(PyQgsVectorFileWriter test_qgsvectorfilewriter.py) ADD_PYTHON_TEST(PyQgsSpatialiteProvider test_qgsspatialiteprovider.py) ADD_PYTHON_TEST(PyQgsZonalStatistics test_qgszonalstatistics.py) - +ADD_PYTHON_TEST(PyQgsAppStartup test_qgsappstartup.py) diff --git a/tests/src/python/test_qgsappstartup.py b/tests/src/python/test_qgsappstartup.py new file mode 100644 index 000000000000..e8d2702e67bd --- /dev/null +++ b/tests/src/python/test_qgsappstartup.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- +"""QGIS Unit tests for QgsApplication. + +.. note:: This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. +""" +__author__ = 'Hugo Mercier (hugo.mercier@oslandia.com)' +__date__ = '17/07/2013' +__copyright__ = 'Copyright 2013, The QGIS Project' +# This will get replaced with a git SHA1 when you do a git archive +__revision__ = '$Format:%H$' + +from PyQt4 import QtGui, QtCore +from qgis.core import QgsApplication +import sys +import os +import time +import locale +import shutil +import subprocess + +from utilities import unittest, getQgisTestApp, unitTestDataPath + +QGISAPP, CANVAS, IFACE, PARENT = getQgisTestApp() + +TEST_DATA_DIR = unitTestDataPath() + +class TestPyQgsAppStartup(unittest.TestCase): + + def doTestOptionsPath(self, option, testDir, testFile, timeOut, env = {}): + """Run QGIS with the given option. Wait for testFile to be created. If time runs out, fail. + """ + # from unicode to local + testDir = str(QtCore.QString( testDir ).toLocal8Bit()) + myTestFile = testDir + "/" + testFile + + if os.path.exists( myTestFile ): + os.remove( myTestFile ) + + # environnement variables = system variables + provided 'env' + myenv = os.environ.copy() + myenv.update( env ) + + p = subprocess.Popen( [ QGIS_BIN, "--nologo", option, testDir ], env = myenv ) + + s = 0 + ok = True + while not os.path.exists( myTestFile ): + time.sleep(1) + s = s+1 + if s > timeOut: + ok = False + break + + p.terminate() + + # remove testDir + shutil.rmtree( testDir, ignore_errors = True ) + return ok + + def testOptionsPath( self ): + for p in [ 'test_config', 'test config', 'test_configé€' ]: + assert self.doTestOptionsPath( "--optionspath", (os.getcwd() + '/' + p).decode('utf-8'), "QGIS/QGIS2.ini", 5 ), "options path %s" % p + + def testConfigPath( self ): + for p in [ 'test_config', 'test config', 'test_configé€' ]: + assert self.doTestOptionsPath( "--configpath", (os.getcwd() + '/' + p).decode('utf-8'), "qgis.db", 30 ), "config path %s" % p + + def testPluginPath( self ): + for t in ['test_plugins', 'test plugins', 'test_pluginsé€' ]: + + # get a unicode test dir + testDir = (os.getcwd() + '/' + t).decode('utf-8') + + # copy from testdata + shutil.rmtree( testDir, ignore_errors = True ) + shutil.copytree( TEST_DATA_DIR + '/test_plugin_path', testDir ) + + # we use here a minimal plugin that writes to 'plugin_started.txt' when it is started + # if QGIS_PLUGINPATH is correctly parsed, this plugin is executed and the file is created + assert self.doTestOptionsPath( "--optionspath", testDir, "plugin_started.txt", 10, { 'QGIS_PLUGINPATH' : str(QtCore.QString(testDir).toLocal8Bit()) } ) + + +if __name__ == '__main__': + + # look for qgis bin path + QGIS_BIN = '' + prefixPath = os.environ['QGIS_PREFIX_PATH'] + # see qgsapplication.cpp:98 + for f in [ '', '/..', '/bin', '/../../..' ]: + testDir = prefixPath + f + if os.path.exists( testDir + '/qgis' ): + QGIS_BIN = testDir + '/qgis' + break + if os.path.exists( testDir + '/qgis.exe' ): + QGIS_BIN = testDir + '/qgis.exe' + break + + print 'QGIS_BIN =', QGIS_BIN + + unittest.main() + diff --git a/tests/testdata/test_plugin_path/PluginPathTest/__init__.py b/tests/testdata/test_plugin_path/PluginPathTest/__init__.py new file mode 100644 index 000000000000..8693412614dc --- /dev/null +++ b/tests/testdata/test_plugin_path/PluginPathTest/__init__.py @@ -0,0 +1,52 @@ +# -*- coding: utf-8 -*- + +import os + +class Test: + + def __init__(self, iface): + plugin_dir = os.path.dirname(__file__) + + # write to a file + f = open( plugin_dir + '/../plugin_started.txt', 'w' ) + f.write("OK\n") + f.close() + + def initGui(self): + pass + + def unload(self): + pass + + # run method that performs all the real work + def run(self): + pass + +def name(): + return "plugin path test" + + +def description(): + return "desc" + + +def version(): + return "Version 0.1" + + +def icon(): + return "icon.png" + + +def qgisMinimumVersion(): + return "2.0" + +def author(): + return "HM/Oslandia" + +def email(): + return "hugo.mercier@oslandia.com" + +def classFactory(iface): + # load Test class from file Test + return Test(iface) diff --git a/tests/testdata/test_plugin_path/PluginPathTest/metadata.txt b/tests/testdata/test_plugin_path/PluginPathTest/metadata.txt new file mode 100644 index 000000000000..079e9ef8219d --- /dev/null +++ b/tests/testdata/test_plugin_path/PluginPathTest/metadata.txt @@ -0,0 +1,7 @@ +[general] +name=plugin path test +qgisMinimumVersion=2.0 +description=desc +version=0.1 +author=HM/Oslandia +email=hugo.mercier@oslandia.com diff --git a/tests/testdata/test_plugin_path/QGIS/QGIS2.ini b/tests/testdata/test_plugin_path/QGIS/QGIS2.ini new file mode 100644 index 000000000000..d7f926bf075a --- /dev/null +++ b/tests/testdata/test_plugin_path/QGIS/QGIS2.ini @@ -0,0 +1,2 @@ +[PythonPlugins] +PluginPathTest=true