[rkward-cvs] SF.net SVN: rkward-code:[4801] trunk/rkward

tfry at users.sf.net tfry at users.sf.net
Fri Sep 5 08:39:37 UTC 2014


Revision: 4801
          http://sourceforge.net/p/rkward/code/4801
Author:   tfry
Date:     2014-09-05 08:39:35 +0000 (Fri, 05 Sep 2014)
Log Message:
-----------
Use binary wrapper executable on all platforms.
The main motivation is to fix issues with paths with spaces on Windows, which apparently is just not doable in a shell script.
This version NOT yet tested on Windows, Windows binary installer will need adjusting, some cleanups to follow.

Modified Paths:
--------------
    trunk/rkward/ChangeLog
    trunk/rkward/doc/rkward/man-rkward.1.docbook
    trunk/rkward/rkward/CMakeLists.txt
    trunk/rkward/rkward/main.cpp
    trunk/rkward/rkward/wrapper/CMakeLists.txt
    trunk/rkward/rkward/wrapper/rkward_startup_wrapper.cpp

Modified: trunk/rkward/ChangeLog
===================================================================
--- trunk/rkward/ChangeLog	2014-09-01 21:32:22 UTC (rev 4800)
+++ trunk/rkward/ChangeLog	2014-09-05 08:39:35 UTC (rev 4801)
@@ -1,3 +1,6 @@
+- Fixed: Problems starting from paths with spaces in the file name on Windows
+- Added command line option --r-executable for switching between several installations of R
+- Use a binary wrapper, instead of wrapper shell script for startup on all platforms
 - Linear regression plugin gains option to save predicted values
 - Fixed some compilation problems
 - Add basic support to export plots using tikzDevice

Modified: trunk/rkward/doc/rkward/man-rkward.1.docbook
===================================================================
--- trunk/rkward/doc/rkward/man-rkward.1.docbook	2014-09-01 21:32:22 UTC (rev 4800)
+++ trunk/rkward/doc/rkward/man-rkward.1.docbook	2014-09-05 08:39:35 UTC (rev 4801)
@@ -34,6 +34,7 @@
 <group choice="opt"><option>--debug-flags</option> <replaceable> flags</replaceable></group>
 <group choice="opt"><option>--debugger</option> <replaceable> debugger_command</replaceable></group>
 <group choice="opt"><option>--backend-debugger</option> <replaceable> debugger_command</replaceable></group>
+<group choice="opt"><option>--r-executable</option> <replaceable> path_to_executable</replaceable></group>
 <arg choice="opt">KDE Generic Options</arg>
 <arg choice="opt">Qt Generic Options</arg>
 </cmdsynopsis>
@@ -63,12 +64,17 @@
 </varlistentry>
 <varlistentry>
 <term><option>--debugger</option> <replaceable>command</replaceable></term>
-<listitem><para>Run &rkward; through the specified debugger command. To add command line options to the debugger command, enclose them in single quotes ('') together with the command. <emphasis>NOTE:</emphasis> To use the \-\-debugger option, it must be specified as the first option on the command line. Only the frontend process will be debugged, using this option.</para></listitem>
+<listitem><para>Run &rkward; through the specified debugger command. To add command line options to the debugger command, enclose them in single quotes ('') together with the command. <emphasis>NOTE:</emphasis> To use the \-\-debugger option, it must be specified as the first option on the command line. Only the frontend process will be debugged, using this option.</para>
+<para>Note that there are a number of pitfalls that may complicate setting up the debugger session as desired. Consider starting &rkward; with option \-\-debug-lebel 3, which will print the effective command line used to start the frontend (but not set some environment variables). Under Windows, the debugger command will <emphasis>not</emphasis> be connected to stdin. For interactive debugging, consider using a graphical debugger.</para></listitem>
 </varlistentry>
 <varlistentry>
 <term><option>--backend-debugger</option> <replaceable>command</replaceable></term>
 <listitem><para>Run the &rkward; backend through the specified debugger command. To add command line options to the debugger command, enclose them in single quotes ('') together with the command. <emphasis>NOTE:</emphasis> Debugger arguments will be split by spaces. If this is not appropriate, you will have to write your own wrapper script for invoking the debugger. Also, make sure to redirect all debugger output and/or input as appropriate. See the examples.</para></listitem>
 </varlistentry>
+<varlistentry>
+<term><option>--r-executable</option> <replaceable>command</replaceable></term>
+<listitem><para>In the case of several R installations, specify the installation to use, e.g. /usr/bin/R. Note that the rkward R library must have been installed to this installation of R, or startup will fail.</para></listitem>
+</varlistentry>
 </variablelist>
 
 </refsect1>
@@ -84,6 +90,8 @@
 <para><programlisting>
 # Run the rkward backend through valgrind
 rkward --backend-debugger 'valgrind --log-file=valgrind.log'.
+# Debug the frontend through gdb
+rkward --debugger 'gdb --args'
 </programlisting></para>
 </refsect1>
 

Modified: trunk/rkward/rkward/CMakeLists.txt
===================================================================
--- trunk/rkward/rkward/CMakeLists.txt	2014-09-01 21:32:22 UTC (rev 4800)
+++ trunk/rkward/rkward/CMakeLists.txt	2014-09-05 08:39:35 UTC (rev 4801)
@@ -64,24 +64,6 @@
 SET_TARGET_PROPERTIES(rkward.frontend PROPERTIES
         MACOSX_BUNDLE_BUNDLE_NAME "RKWard")
 
-# wrapper script
-GET_DIRECTORY_PROPERTY(R_EXECUTABLE DIRECTORY rbackend DEFINITION R_EXECUTABLE)
-IF(WIN32)
-	SET(RKWARD_WRAPPER_TEMPLATE rkward.bat.in)
-	SET(RKWARD_WRAPPER_SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/rkward.bat)
-# For now use wrapper binary on Windows, only, and only as an alternative to rkward.bat.
-# Will become standard startup mechanism on all platforms, in a later release
-	ADD_SUBDIRECTORY( wrapper )
-ELSE(WIN32)
-	SET(RKWARD_WRAPPER_TEMPLATE rkward.sh.in)
-	SET(RKWARD_WRAPPER_SCRIPT ${CMAKE_CURRENT_BINARY_DIR}/rkward)
-ENDIF(WIN32)
-CONFIGURE_FILE(
-	${RKWARD_WRAPPER_TEMPLATE}
-	${RKWARD_WRAPPER_SCRIPT}
-	@ONLY)
-ADD_DEPENDENCIES(rkward.frontend ${RKWARD_WRAPPER_SCRIPT})
-
 IF(Q_WS_MAC)
 	SET(RKWARD_INFOPLIST_TEMPLATE Info.plist.in)
 	SET(RKWARD_INFOPLIST_FILE ${CMAKE_SOURCE_DIR}/rkward/Info.plist)
@@ -93,6 +75,20 @@
 
 TARGET_LINK_LIBRARIES(rkward.frontend ${KDE4_KDECORE_LIBS} windows ${RKWARD_ADDLIBS} agents dialogs plugin settings dataeditor core scriptbackends rbackend misc ktexteditor ${KDE4_KHTML_LIBS} ${KDE4_KFILE_LIBS} ${KDE4_KDEUI_LIBS} ${KDE4_KROSSCORE_LIBS} ${QT_QTSCRIPT_LIBRARY} ${QT_QTNETWORK_LIBRARY})
 
+# wrapper executable
+GET_DIRECTORY_PROPERTY(R_EXECUTABLE DIRECTORY rbackend DEFINITION R_EXECUTABLE)
+IF (WIN32)
+	SET (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
+ENDIF (WIN32)
+ADD_CUSTOM_COMMAND (OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rkward.ico"
+			COMMAND cmake -E copy "${CMAKE_CURRENT_SOURCE_DIR}/icons/app-icon/rkward.ico"
+			"${CMAKE_CURRENT_BINARY_DIR}/rkward.ico")
+ADD_EXECUTABLE (rkward wrapper/rkward_startup_wrapper.cpp wrapper/rkward_windows_icon.rc rkward.ico)
+add_definitions ("-DR_EXECUTABLE=\\\"${R_EXECUTABLE}\\\"")
+TARGET_LINK_LIBRARIES(rkward ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY})
+
+ADD_DEPENDENCIES(rkward.frontend rkward)
+
 ########### install files ###############
 
 INSTALL(DIRECTORY plugins/ pages DESTINATION ${DATA_INSTALL_DIR}/rkward
@@ -102,12 +98,12 @@
 
 IF(Q_WS_MAC)
 	INSTALL(TARGETS rkward.frontend DESTINATION ${BIN_INSTALL_DIR})
-	INSTALL(PROGRAMS ${RKWARD_WRAPPER_SCRIPT} DESTINATION ${BUNDLE_INSTALL_DIR}/${CPACK_BUNDLE_NAME}.app/Contents/MacOS)
+	INSTALL(TARGETS rkward DESTINATION ${BUNDLE_INSTALL_DIR}/${CPACK_BUNDLE_NAME}.app/Contents/MacOS)
 	INSTALL(FILES ${CMAKE_SOURCE_DIR}/rkward/icons/app-icon/rkward.icns DESTINATION ${BUNDLE_INSTALL_DIR}/${CPACK_BUNDLE_NAME}.app/Contents/Resources)
 	INSTALL(FILES ${RKWARD_INFOPLIST_FILE} DESTINATION ${BUNDLE_INSTALL_DIR}/${CPACK_BUNDLE_NAME}.app/Contents)
 ELSE(Q_WS_MAC)
 	INSTALL(TARGETS rkward.frontend DESTINATION ${LIBEXEC_INSTALL_DIR})
-	INSTALL(PROGRAMS ${RKWARD_WRAPPER_SCRIPT} DESTINATION ${BIN_INSTALL_DIR})
+	INSTALL(TARGETS rkward DESTINATION ${BIN_INSTALL_DIR})
 ENDIF(Q_WS_MAC)
 
 INSTALL(FILES rkward.desktop DESTINATION ${XDG_APPS_INSTALL_DIR})

Modified: trunk/rkward/rkward/main.cpp
===================================================================
--- trunk/rkward/rkward/main.cpp	2014-09-01 21:32:22 UTC (rev 4800)
+++ trunk/rkward/rkward/main.cpp	2014-09-05 08:39:35 UTC (rev 4801)
@@ -111,12 +111,17 @@
 	RKDebugMessageWindow::newMessage (flags, level, QString (buffer));
 }
 
+QString decodeArgument (const QString &input) {
+	return (QUrl::fromPercentEncoding (input.toUtf8()));
+}
+
 int main(int argc, char *argv[]) {
 	options.add ("evaluate <Rcode>", ki18n ("After starting (and after loading the specified workspace, if applicable), evaluate the given R code."), 0);
 	options.add ("debug-level <level>", ki18n ("Verbosity of debug messages (0-5)"), "2");
 	options.add ("debug-flags <flags>", ki18n ("Mask for components to debug (see debug.h)"), QString::number (DEBUG_ALL).toLocal8Bit ());
 	options.add ("debugger <command>", ki18n ("Debugger (enclose any debugger arguments in single quotes ('') together with the command)"), "");
 	options.add ("backend-debugger <command>", ki18n ("Debugger for the backend. (Enclose any debugger arguments in single quotes ('') together with the command. Make sure to re-direct stdout!)"), "");
+	options.add ("r-executable <command>", ki18n ("Use specified R installation, instead of the one configured at compile time (note: rkward R library must be installed to that installation of R)"), "");
 	options.add ("+[File]", ki18n ("R workspace file to open"), 0);
 
 	KAboutData aboutData("rkward", QByteArray (), ki18n ("RKWard"), RKWARD_VERSION, ki18n ("Frontend to the R statistics language"), KAboutData::License_GPL, ki18n ("(c) 2002, 2004 - 2013"), KLocalizedString (), "http://rkward.sf.net", "rkward-devel at lists.sourceforge.net");
@@ -159,10 +164,10 @@
 	}
 
 	if (args->count ()) {
-		RKGlobals::startup_options["initial_url"] = QUrl (args->url (0));
+		RKGlobals::startup_options["initial_url"] = QUrl (KCmdLineArgs::makeURL (decodeArgument (args->arg (0)).toUtf8 ()));
 	}
-	RKGlobals::startup_options["evaluate"] = args->getOption ("evaluate");
-	RKGlobals::startup_options["backend-debugger"] = args->getOption ("backend-debugger");
+	RKGlobals::startup_options["evaluate"] = decodeArgument (args->getOption ("evaluate"));
+	RKGlobals::startup_options["backend-debugger"] = decodeArgument (args->getOption ("backend-debugger"));
 
 	RKWardApplication app;
 	// install message handler *after* the componentData has been initialized

Modified: trunk/rkward/rkward/wrapper/CMakeLists.txt
===================================================================
--- trunk/rkward/rkward/wrapper/CMakeLists.txt	2014-09-01 21:32:22 UTC (rev 4800)
+++ trunk/rkward/rkward/wrapper/CMakeLists.txt	2014-09-05 08:39:35 UTC (rev 4801)
@@ -1,12 +1,2 @@
-IF (WIN32)
-	SET (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -static")
-ENDIF (WIN32)
-ADD_CUSTOM_COMMAND (OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/rkward.ico"
-			COMMAND cmake -E copy "${CMAKE_CURRENT_SOURCE_DIR}/../icons/app-icon/rkward.ico"
-			"${CMAKE_CURRENT_BINARY_DIR}/rkward.ico")
-ADD_EXECUTABLE (rkward rkward_startup_wrapper.cpp rkward_windows_icon.rc rkward.ico)
-add_definitions ("-DR_EXECUTABLE=\\\"${R_EXECUTABLE}\\\"")
-TARGET_LINK_LIBRARIES(rkward ${QT_QTGUI_LIBRARY} ${QT_QTCORE_LIBRARY})
-IF (WIN32)	# NOTE: On other platforms, currently in conflict with shell wrapper script
-	INSTALL(TARGETS rkward DESTINATION ${BIN_INSTALL_DIR})
-ENDIF (WIN32)
+
+#INSTALL(TARGETS rkward DESTINATION ${BIN_INSTALL_DIR})

Modified: trunk/rkward/rkward/wrapper/rkward_startup_wrapper.cpp
===================================================================
--- trunk/rkward/rkward/wrapper/rkward_startup_wrapper.cpp	2014-09-01 21:32:22 UTC (rev 4800)
+++ trunk/rkward/rkward/wrapper/rkward_startup_wrapper.cpp	2014-09-05 08:39:35 UTC (rev 4801)
@@ -2,7 +2,7 @@
                           rkward_startup_wrapper  -  description
                              -------------------
     begin                : Sun Mar 10 2013
-    copyright            : (C) 2013 by Thomas Friedrichsmeier
+    copyright            : (C) 2013, 2014 by Thomas Friedrichsmeier
     email                : tfry at users.sourceforge.net
  ***************************************************************************/
 
@@ -26,9 +26,11 @@
 #include <QDir>
 #include <QProcess>
 #include <QSettings>
+#include <QUrl>
+#include <QFile>
 
-#ifndef RKWARD_REL_INSTALL_PATH
-#	define RKWARD_REL_INSTALL_PATH ""
+#ifndef RKWARD_FRONTEND_LOCATION
+#	define RKWARD_FRONTEND_LOCATION ""
 #endif
 
 #ifndef R_EXECUTABLE
@@ -49,15 +51,77 @@
 	return findExeAtPath ("rkward.frontend", path);
 }
 
+QString quoteCommand (const QString &orig) {
+#ifdef Q_WS_WIN
+	QString ret = "\"";
+	for (int i = 0; i < orig.size (); ++i) {
+		if (orig[i] == QLatin1Char ('"')) ret.append ("\"\"");
+		else if (orig[i] == QLatin1Char ('\\')) ret.append ("\\\\");
+		else ret.append (orig[i]);
+	}
+	ret.append (QLatin1Char ('"'));
+	return ret;
+#else
+	return orig;
+#endif
+}
+
+#ifndef Q_WS_WIN
+// see http://blog.qt.digia.com/blog/2006/03/16/starting-interactive-processes-with-qprocess/
+// Need an interactive process e.g. for running through gdb
+#	include <unistd.h>
+class InteractiveProcess : public QProcess {
+    static int stdinClone;
+public:
+    InteractiveProcess (QObject *parent = 0) : QProcess (parent) {
+        if (stdinClone == -1) stdinClone = ::dup (fileno(stdin));
+    }
+protected:
+    void setupChildProcess () {
+        ::dup2 (stdinClone, fileno(stdin));
+    }
+};
+int InteractiveProcess::stdinClone = -1;
+#else
+// no easy solution for Windows. But ain't Windows the world of graphical debuggers, anyway...
+#	define InteractiveProcess QProcess
+#endif
+
 int main (int argc, char *argv[]) {
 	QApplication app (argc, argv);
 	QStringList args = app.arguments ();
 	if (!args.isEmpty ()) args.pop_front ();	// The command itself
 
+	// Parse arguments that need handling in the wrapper
+	bool usage = false;
+	QString debugger_arg;
+	QString r_exe_arg;
+	int debug_level = 2;
+
+	for (int i=0; i < args.size (); ++i) {
+		if (args[i] == "--debugger") {
+			if ((i+1) < args.size ()) {
+				debugger_arg = args.takeAt (i + 1);
+			} else usage = true;
+			args.removeAt (i);
+			--i;
+		} else if (args[i] == "--r-executable") {
+			if ((i+1) < args.size ()) {
+				r_exe_arg = args.takeAt (i + 1);
+			} else usage = true;
+			args.removeAt (i);
+			--i;
+		} else if (args[i] == "--debug-level") {
+			if ((i+1) < args.size ()) {
+				debug_level = args[i+1].toInt ();
+			}
+		}
+	}
+
+	// Locate KDE and RKWard installations
 	QString kdeinit4_exe;
 	QString rkward_frontend_exe;
-	rkward_frontend_exe = findRKWardAtPath (app.applicationDirPath ());	// these two are for running directly from a build tree
-	if (rkward_frontend_exe.isNull ()) rkward_frontend_exe = findRKWardAtPath (QDir (app.applicationDirPath ()).filePath (".."));	// these two are for running directly from a build tree
+	rkward_frontend_exe = findRKWardAtPath (app.applicationDirPath ());	// this is for running directly from a build tree
 	if (rkward_frontend_exe.isNull ()) {	// this is for the regular case: startup wrapper is not in the same dir as rkward.frontend
 		QString kde4_config_exe;
 		kde4_config_exe = findExeAtPath ("kde4-config", QDir::currentPath ());
@@ -74,13 +138,15 @@
 #ifdef Q_WS_WIN
 		kdeinit4_exe = findExeAtPath ("kdeinit4", kde_dir.path ());
 		qputenv ("PATH", QString (kde_dir.path () + ";" + qgetenv ("PATH")).toLocal8Bit ());
+		if (debug_level > 3) qDebug ("Adding %s to the system path", qPrintable (kde_dir.path ()));
 #endif
 		// important if RKWard is not in KDEPREFIX/bin but e.g. KDEPREFIX/lib/libexec
 		qputenv ("RKWARD_ENSURE_PREFIX", kde_dir.path().toLocal8Bit ());
+		if (debug_level > 3) qDebug ("Setting environment variable RKWARD_ENSURE_PREFIX=%s", qPrintable (kde_dir.path ()));
 	
-		rkward_frontend_exe = findRKWardAtPath (kde_dir.absoluteFilePath ("bin"));
+		rkward_frontend_exe = findRKWardAtPath (RKWARD_FRONTEND_LOCATION);
+		if (rkward_frontend_exe.isNull ()) rkward_frontend_exe = findRKWardAtPath (kde_dir.absoluteFilePath ("bin"));
 		if (rkward_frontend_exe.isNull ()) rkward_frontend_exe = findRKWardAtPath (kde_dir.absoluteFilePath ("../lib/libexec"));
-		if (rkward_frontend_exe.isNull ()) rkward_frontend_exe = findRKWardAtPath (kde_dir.absoluteFilePath (RKWARD_REL_INSTALL_PATH));
 
 		if (rkward_frontend_exe.isNull ()) {
 			QMessageBox::critical (0, "RKWard frontend binary missing", "RKWard frontend binary could not be found. When moving / copying RKWard, make sure to copy the whole application folder, or create a shorcut / link, instead.");
@@ -88,6 +154,11 @@
 		}
 	}
 
+	if (usage) {
+		QProcess::execute (rkward_frontend_exe, QStringList ("--help"));
+		exit (1);
+	}
+
 #ifdef Q_WS_WIN
 	// Explicit initialization of KDE, in case Windows 7 asks for admin priviledges
 	if (kdeinit4_exe.isNull ()) {
@@ -96,31 +167,6 @@
 	if (!kdeinit4_exe.isNull ()) QProcess::execute (kdeinit4_exe, QStringList ());
 #endif
 
-	bool usage = false;
-	QString debugger_arg;
-	QString r_exe_arg;
-
-	for (int i=0; i < args.size (); ++i) {
-		if (args[i] == "--debugger") {
-			if ((i+1) < args.size ()) {
-				debugger_arg = args.takeAt (i + 1);
-			} else usage = true;
-			args.removeAt (i);
-			--i;
-		} else if (args[i] == "--R") {
-			if ((i+1) < args.size ()) {
-				r_exe_arg = args.takeAt (i + 1);
-			} else usage = true;
-			args.removeAt (i);
-			--i;
-		}
-	}
-
-	if (usage) {
-		QProcess::execute (rkward_frontend_exe, QStringList ("--help"));
-		exit (1);
-	}
-
 	// Look for R:
 	//- command line parameter
 	//- Specified in cfg file next to rkward executable
@@ -131,6 +177,7 @@
 			QMessageBox::critical (0, "Specified R executable does not exist", QString ("The R executable specified on the command line (%1) does not exist or is not executable.").arg (r_exe));
 			exit (1);
 		}
+		if (debug_level > 3) qDebug ("Using R specified on command line");
 	} else {
 		QFileInfo frontend_info (rkward_frontend_exe);
 		QDir frontend_path = frontend_info.absoluteDir ();
@@ -147,6 +194,7 @@
 					exit (1);
 				}
 			}
+			if (debug_level > 3) qDebug ("Using R as configured in config file %s", qPrintable (rkward_ini_file.absoluteFilePath ()));
 		}
 		if (r_exe.isNull ()) {
 			r_exe = R_EXECUTABLE;
@@ -154,14 +202,31 @@
 				QMessageBox::critical (0, "Specified R executable does not exist", QString ("The R executable specified at compile time (%1) does not exist or is not executable. Probably the installation of R has moved. You can use the command line parameter '--R', or supply an rkward.ini file to specify the new location.").arg (r_exe));
 				exit (1);
 			}
+			if (debug_level > 3) qDebug ("Using R as configured at compile time");
 		}
 	}
 
 	qputenv ("R_BINARY", r_exe.toLocal8Bit ());
 	QStringList call_args ("CMD");
-	if (!debugger_arg.isNull ()) call_args.append (debugger_arg);
-	call_args.append (rkward_frontend_exe);
-	call_args.append (args);
-	
-	return (QProcess::execute (r_exe, call_args));
+	if (!debugger_arg.isNull ()) call_args.append (debugger_arg.split (" "));
+	call_args.append (quoteCommand (rkward_frontend_exe));
+
+	if (!args.isEmpty ()) {
+		// NOTE: QProcess quotes its arguments, *but* properly passing all spaces and quotes through the R CMD wrapper, seems near(?) impossible on Windows. Instead, we use percent encoding, internally.
+		for (int i = 0; i < args.size (); ++i) {
+			call_args.append (QString::fromUtf8 (QUrl::toPercentEncoding (args[i], QByteArray (), " \"")));
+		}
+	}
+
+	if (debug_level > 2) qDebug ("Starting frontend: %s %s", qPrintable (r_exe), qPrintable (call_args.join (" ")));
+
+	InteractiveProcess proc;
+	proc.setProcessChannelMode (QProcess::ForwardedChannels);
+	proc.start (quoteCommand (r_exe), call_args);
+	bool ok = proc.waitForFinished (-1);
+	if (proc.exitCode () || !ok) {
+		QMessageBox::critical (0, "Error starting RKWard", QString ("Starting RKWard failed with error \"%1\"").arg (proc.errorString ()));
+	}
+
+	return (0);
 }





More information about the rkward-tracker mailing list