From c68ebeed2e47c7a78d790bb732a080edaf07d9ba Mon Sep 17 00:00:00 2001
From: hjk <hjk121@nokiamail.com>
Date: Wed, 5 Feb 2014 10:43:21 +0100
Subject: [PATCH] QtcProcess: Introduce a QtcProcess::Arguments class

This is used to get a platform-agnostic handle on "command line
arguments". It essentially wraps a single QString on Windows,
and a QStringList everywhere else.

As a consequence, several occurrences of #ifdef Q_OS_*
can be removed from the codebase.

Change-Id: Ic93118c1bd0bce0ebb58f416d395dbaebb861772
Reviewed-by: Oswald Buddenhagen <oswald.buddenhagen@digia.com>
---
 src/libs/utils/consoleprocess_unix.cpp        |   34 +-
 src/libs/utils/consoleprocess_win.cpp         |    5 +-
 src/libs/utils/qtcprocess.cpp                 | 1311 +++++++++--------
 src/libs/utils/qtcprocess.h                   |   63 +-
 src/plugins/coreplugin/fileutils.cpp          |    7 +-
 src/plugins/debugger/gdb/gdbengine.cpp        |   25 +-
 src/plugins/debugger/lldb/lldbengine.cpp      |   12 +-
 src/plugins/git/gitclient.cpp                 |    2 +-
 src/plugins/ios/iosrunconfiguration.cpp       |   33 +-
 src/plugins/projectexplorer/gcctoolchain.cpp  |    9 +-
 .../projectexplorer/processparameters.cpp     |   14 +-
 .../remotelinux/linuxdeviceprocess.cpp        |    2 +-
 .../remotelinuxrunconfiguration.cpp           |    2 +-
 tests/auto/qtcprocess/tst_qtcprocess.cpp      |  912 ++++++------
 14 files changed, 1246 insertions(+), 1185 deletions(-)

diff --git a/src/libs/utils/consoleprocess_unix.cpp b/src/libs/utils/consoleprocess_unix.cpp
index 331e65a0ae..cc9474dbb5 100644
--- a/src/libs/utils/consoleprocess_unix.cpp
+++ b/src/libs/utils/consoleprocess_unix.cpp
@@ -75,7 +75,8 @@ bool ConsoleProcess::start(const QString &program, const QString &args)
         return false;
 
     QtcProcess::SplitError perr;
-    QStringList pargs = QtcProcess::prepareArgs(args, &perr, &d->m_environment, &d->m_workingDir);
+    QtcProcess::Arguments pargs = QtcProcess::prepareArgs(args, &perr, HostOsInfo::hostOs(),
+                                                          &d->m_environment, &d->m_workingDir);
     QString pcmd;
     if (perr == QtcProcess::SplitOk) {
         pcmd = program;
@@ -91,12 +92,15 @@ bool ConsoleProcess::start(const QString &program, const QString &args)
             return false;
         }
         pcmd = QLatin1String("/bin/sh");
-        pargs << QLatin1String("-c") << (QtcProcess::quoteArg(program) + QLatin1Char(' ') + args);
+        pargs = QtcProcess::Arguments::createUnixArgs(QStringList()
+                    << QLatin1String("-c")
+                    << (QtcProcess::quoteArg(program) + QLatin1Char(' ') + args));
     }
 
     QtcProcess::SplitError qerr;
-    QStringList xtermArgs = QtcProcess::prepareArgs(terminalEmulator(d->m_settings), &qerr,
-                                                    &d->m_environment, &d->m_workingDir);
+    QtcProcess::Arguments xtermArgs = QtcProcess::prepareArgs(terminalEmulator(d->m_settings), &qerr,
+                                                              HostOsInfo::hostOs(),
+                                                              &d->m_environment, &d->m_workingDir);
     if (qerr != QtcProcess::SplitOk) {
         emit processError(qerr == QtcProcess::BadQuoting
                           ? tr("Quoting error in terminal command.")
@@ -134,23 +138,23 @@ bool ConsoleProcess::start(const QString &program, const QString &args)
         }
     }
 
-    if (Utils::HostOsInfo::isMacHost()) {
-        xtermArgs << (QCoreApplication::applicationDirPath()
-                      + QLatin1String("/../Resources/qtcreator_process_stub"));
-    } else {
-        xtermArgs << (QCoreApplication::applicationDirPath()
-                      + QLatin1String("/qtcreator_process_stub"));
-    }
-    xtermArgs
+    QString stubPath = QCoreApplication::applicationDirPath();
+    if (Utils::HostOsInfo::isMacHost())
+        stubPath.append(QLatin1String("/../Resources/qtcreator_process_stub"));
+    else
+        stubPath.append(QLatin1String("/qtcreator_process_stub"));
+
+    QStringList allArgs = xtermArgs.toUnixArgs();
+    allArgs << stubPath
               << modeOption(d->m_mode)
               << d->m_stubServer.fullServerName()
               << msgPromptToClose()
               << workingDirectory()
               << (d->m_tempFile ? d->m_tempFile->fileName() : QString())
-              << pcmd << pargs;
+              << pcmd << pargs.toUnixArgs();
 
-    QString xterm = xtermArgs.takeFirst();
-    d->m_process.start(xterm, xtermArgs);
+    QString xterm = allArgs.takeFirst();
+    d->m_process.start(xterm, allArgs);
     if (!d->m_process.waitForStarted()) {
         stubServerShutdown();
         emit processError(tr("Cannot start the terminal emulator '%1', change the setting in the "
diff --git a/src/libs/utils/consoleprocess_win.cpp b/src/libs/utils/consoleprocess_win.cpp
index 646cf11806..9c6df8104b 100644
--- a/src/libs/utils/consoleprocess_win.cpp
+++ b/src/libs/utils/consoleprocess_win.cpp
@@ -75,7 +75,10 @@ bool ConsoleProcess::start(const QString &program, const QString &args)
         pcmd = program;
         pargs = args;
     } else {
-        QtcProcess::prepareCommand(program, args, &pcmd, &pargs, &d->m_environment, &d->m_workingDir);
+        QtcProcess::Arguments outArgs;
+        QtcProcess::prepareCommand(program, args, &pcmd, &outArgs, OsTypeWindows,
+                                   &d->m_environment, &d->m_workingDir);
+        pargs = outArgs.toWindowsArgs();
     }
 
     const QString err = stubServerListen();
diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp
index fed14afa90..b6e13b1cca 100644
--- a/src/libs/utils/qtcprocess.cpp
+++ b/src/libs/utils/qtcprocess.cpp
@@ -31,16 +31,43 @@
 #include "stringutils.h"
 
 #include <utils/qtcassert.h>
+#include <utils/hostosinfo.h>
 
 #include <QDir>
 #include <QDebug>
 #include <QCoreApplication>
+#include <QStack>
 
 #ifdef Q_OS_WIN
 #include <qt_windows.h>
 #endif
 
-using namespace Utils;
+
+// The main state of the Unix shell parser
+enum MxQuoting { MxBasic, MxSingleQuote, MxDoubleQuote, MxParen, MxSubst, MxGroup, MxMath };
+
+struct MxState
+{
+    MxQuoting current;
+    // Bizarrely enough, double quoting has an impact on the behavior of some
+    // complex expressions within the quoted string.
+    bool dquote;
+};
+QT_BEGIN_NAMESPACE
+Q_DECLARE_TYPEINFO(MxState, Q_PRIMITIVE_TYPE);
+QT_END_NAMESPACE
+
+// Pushed state for the case where a $(()) expansion turns out bogus
+struct MxSave
+{
+    QString str;
+    int pos, varPos;
+};
+QT_BEGIN_NAMESPACE
+Q_DECLARE_TYPEINFO(MxSave, Q_MOVABLE_TYPE);
+QT_END_NAMESPACE
+
+namespace Utils {
 
 /*!
     \class Utils::QtcProcess
@@ -49,9 +76,7 @@ using namespace Utils;
     shell-quoted process arguments.
 */
 
-#ifdef Q_OS_WIN
-
-inline static bool isMetaChar(ushort c)
+inline static bool isMetaCharWin(ushort c)
 {
     static const uchar iqm[] = {
         0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50,
@@ -61,7 +86,7 @@ inline static bool isMetaChar(ushort c)
     return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
 }
 
-static void envExpand(QString &args, const Environment *env, const QString *pwd)
+static void envExpandWin(QString &args, const Environment *env, const QString *pwd)
 {
     static const QString cdName = QLatin1String("CD");
     int off = 0;
@@ -82,18 +107,18 @@ static void envExpand(QString &args, const Environment *env, const QString *pwd)
     }
 }
 
-QString QtcProcess::prepareArgs(const QString &_args, SplitError *err,
-                                const Environment *env, const QString *pwd)
+static QtcProcess::Arguments prepareArgsWin(const QString &_args, QtcProcess::SplitError *err,
+                                            const Environment *env, const QString *pwd)
 {
     QString args(_args);
 
     if (env) {
-        envExpand(args, env, pwd);
+        envExpandWin(args, env, pwd);
     } else {
         if (args.indexOf(QLatin1Char('%')) >= 0) {
             if (err)
-                *err = FoundMeta;
-            return QString();
+                *err = QtcProcess::FoundMeta;
+            return QtcProcess::Arguments::createWindowsArgs(QString());
         }
     }
 
@@ -109,24 +134,24 @@ QString QtcProcess::prepareArgs(const QString &_args, SplitError *err,
                 if (++p == args.length())
                     break; // For cmd, this is no error.
             } while (args.unicode()[p].unicode() != '"');
-        } else if (isMetaChar(c)) {
+        } else if (isMetaCharWin(c)) {
             if (err)
-                *err = FoundMeta;
-            return QString();
+                *err = QtcProcess::FoundMeta;
+            return QtcProcess::Arguments::createWindowsArgs(QString());
         }
     }
 
     if (err)
-        *err = SplitOk;
-    return args;
+        *err = QtcProcess::SplitOk;
+    return QtcProcess::Arguments::createWindowsArgs(args);
 }
 
-inline static bool isWhiteSpace(ushort c)
+inline static bool isWhiteSpaceWin(ushort c)
 {
     return c == ' ' || c == '\t';
 }
 
-static QStringList doSplitArgs(const QString &args, QtcProcess::SplitError *err)
+static QStringList doSplitArgsWin(const QString &args, QtcProcess::SplitError *err)
 {
     QStringList ret;
 
@@ -139,7 +164,7 @@ static QStringList doSplitArgs(const QString &args, QtcProcess::SplitError *err)
         forever {
             if (p == length)
                 return ret;
-            if (!isWhiteSpace(args.unicode()[p].unicode()))
+            if (!isWhiteSpaceWin(args.unicode()[p].unicode()))
                 break;
             ++p;
         }
@@ -181,7 +206,7 @@ static QStringList doSplitArgs(const QString &args, QtcProcess::SplitError *err)
             while (--bslashes >= 0)
                 arg.append(QLatin1Char('\\'));
 
-            if (p == length || (!inquote && isWhiteSpace(args.unicode()[p].unicode()))) {
+            if (p == length || (!inquote && isWhiteSpaceWin(args.unicode()[p].unicode()))) {
                 ret.append(arg);
                 if (inquote) {
                     if (err)
@@ -258,28 +283,29 @@ static QStringList doSplitArgs(const QString &args, QtcProcess::SplitError *err)
     \c{foo " bar}.
  */
 
-QStringList QtcProcess::splitArgs(const QString &_args, bool abortOnMeta, SplitError *err,
-                                  const Environment *env, const QString *pwd)
+
+static QStringList splitArgsWin(const QString &_args, bool abortOnMeta,
+                                QtcProcess::SplitError *err,
+                                const Environment *env, const QString *pwd)
 {
     if (abortOnMeta) {
-        SplitError perr;
+        QtcProcess::SplitError perr;
         if (!err)
             err = &perr;
-        QString args = prepareArgs(_args, &perr, env, pwd);
-        if (*err != SplitOk)
+        QString args = prepareArgsWin(_args, &perr, env, pwd).toWindowsArgs();
+        if (*err != QtcProcess::SplitOk)
             return QStringList();
-        return doSplitArgs(args, err);
+        return doSplitArgsWin(args, err);
     } else {
         QString args = _args;
         if (env)
-            envExpand(args, env, pwd);
-        return doSplitArgs(args, err);
+            envExpandWin(args, env, pwd);
+        return doSplitArgsWin(args, err);
     }
 }
 
-#else // Q_OS_WIN
 
-inline static bool isMeta(QChar cUnicode)
+static bool isMetaUnix(QChar cUnicode)
 {
     static const uchar iqm[] = {
         0x00, 0x00, 0x00, 0x00, 0xdc, 0x07, 0x00, 0xd8,
@@ -291,8 +317,9 @@ inline static bool isMeta(QChar cUnicode)
     return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
 }
 
-QStringList QtcProcess::splitArgs(const QString &args, bool abortOnMeta, SplitError *err,
-                                  const Environment *env, const QString *pwd)
+static QStringList splitArgsUnix(const QString &args, bool abortOnMeta,
+                                 QtcProcess::SplitError *err,
+                                 const Environment *env, const QString *pwd)
 {
     static const QString pwdName = QLatin1String("PWD");
     QStringList ret;
@@ -448,7 +475,7 @@ QStringList QtcProcess::splitArgs(const QString &args, bool abortOnMeta, SplitEr
                     if (pos >= args.length())
                         goto quoteerr;
                     c = args.unicode()[pos++];
-                } else if (abortOnMeta && isMeta(c)) {
+                } else if (abortOnMeta && isMetaUnix(c)) {
                     goto metaerr;
                 }
                 cret += c;
@@ -466,22 +493,20 @@ QStringList QtcProcess::splitArgs(const QString &args, bool abortOnMeta, SplitEr
 
   okret:
     if (err)
-        *err = SplitOk;
+        *err = QtcProcess::SplitOk;
     return ret;
 
   quoteerr:
     if (err)
-        *err = BadQuoting;
+        *err = QtcProcess::BadQuoting;
     return QStringList();
 
   metaerr:
     if (err)
-        *err = FoundMeta;
+        *err = QtcProcess::FoundMeta;
     return QStringList();
 }
 
-#endif // Q_OS_WIN
-
 inline static bool isSpecialCharUnix(ushort c)
 {
     // Chars that should be quoted (TM). This includes:
@@ -501,6 +526,16 @@ inline static bool hasSpecialCharsUnix(const QString &arg)
     return false;
 }
 
+QStringList QtcProcess::splitArgs(const QString &args, OsType osType,
+                                  bool abortOnMeta, QtcProcess::SplitError *err,
+                                  const Environment *env, const QString *pwd)
+{
+    if (osType == OsTypeWindows)
+        return splitArgsWin(args, abortOnMeta, err, env, pwd);
+    else
+        return splitArgsUnix(args, abortOnMeta, err, env, pwd);
+}
+
 QString QtcProcess::quoteArgUnix(const QString &arg)
 {
     if (!arg.length())
@@ -515,23 +550,7 @@ QString QtcProcess::quoteArgUnix(const QString &arg)
     return ret;
 }
 
-void QtcProcess::addArgUnix(QString *args, const QString &arg)
-{
-    if (!args->isEmpty())
-        *args += QLatin1Char(' ');
-    *args += quoteArgUnix(arg);
-}
-
-QString QtcProcess::joinArgsUnix(const QStringList &args)
-{
-    QString ret;
-    foreach (const QString &arg, args)
-        addArgUnix(&ret, arg);
-    return ret;
-}
-
-#ifdef Q_OS_WIN
-inline static bool isSpecialChar(ushort c)
+static bool isSpecialCharWin(ushort c)
 {
     // Chars that should be quoted (TM). This includes:
     // - control chars & space
@@ -545,21 +564,21 @@ inline static bool isSpecialChar(ushort c)
     return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
 }
 
-inline static bool hasSpecialChars(const QString &arg)
+static bool hasSpecialCharsWin(const QString &arg)
 {
     for (int x = arg.length() - 1; x >= 0; --x)
-        if (isSpecialChar(arg.unicode()[x].unicode()))
+        if (isSpecialCharWin(arg.unicode()[x].unicode()))
             return true;
     return false;
 }
 
-QString QtcProcess::quoteArg(const QString &arg)
+static QString quoteArgWin(const QString &arg)
 {
     if (!arg.length())
         return QString::fromLatin1("\"\"");
 
     QString ret(arg);
-    if (hasSpecialChars(ret)) {
+    if (hasSpecialCharsWin(ret)) {
         // Quotes are escaped and their preceding backslashes are doubled.
         // It's impossible to escape anything inside a quoted string on cmd
         // level, so the outer quoting must be "suspended".
@@ -578,21 +597,38 @@ QString QtcProcess::quoteArg(const QString &arg)
     return ret;
 }
 
-void QtcProcess::addArg(QString *args, const QString &arg)
+QtcProcess::Arguments QtcProcess::prepareArgs(const QString &cmd, SplitError *err, OsType osType,
+                                   const Environment *env, const QString *pwd)
+{
+    if (osType == OsTypeWindows)
+        return prepareArgsWin(cmd, err, env, pwd);
+    else
+        return Arguments::createUnixArgs(splitArgs(cmd, osType, true, err, env, pwd));
+}
+
+
+QString QtcProcess::quoteArg(const QString &arg, OsType osType)
+{
+    if (osType == OsTypeWindows)
+        return quoteArgWin(arg);
+    else
+        return quoteArgUnix(arg);
+}
+
+void QtcProcess::addArg(QString *args, const QString &arg, OsType osType)
 {
     if (!args->isEmpty())
         *args += QLatin1Char(' ');
-    *args += quoteArg(arg);
+    *args += quoteArg(arg, osType);
 }
 
-QString QtcProcess::joinArgs(const QStringList &args)
+QString QtcProcess::joinArgs(const QStringList &args, OsType osType)
 {
     QString ret;
     foreach (const QString &arg, args)
-        addArg(&ret, arg);
+        addArg(&ret, arg, osType);
     return ret;
 }
-#endif
 
 void QtcProcess::addArgs(QString *args, const QString &inArgs)
 {
@@ -609,44 +645,36 @@ void QtcProcess::addArgs(QString *args, const QStringList &inArgs)
         addArg(args, arg);
 }
 
-#ifdef Q_OS_WIN
-void QtcProcess::prepareCommand(const QString &command, const QString &arguments,
-                                QString *outCmd, QString *outArgs,
-                                const Environment *env, const QString *pwd)
-{
-    QtcProcess::SplitError err;
-    *outArgs = QtcProcess::prepareArgs(arguments, &err, env, pwd);
-    if (err == QtcProcess::SplitOk) {
-        *outCmd = command;
-    } else {
-        *outCmd = QString::fromLatin1(qgetenv("COMSPEC"));
-        *outArgs = QLatin1String("/v:off /s /c \"")
-                + quoteArg(QDir::toNativeSeparators(command)) + QLatin1Char(' ') + arguments
-                + QLatin1Char('"');
-    }
-}
-#else
 bool QtcProcess::prepareCommand(const QString &command, const QString &arguments,
-                                QString *outCmd, QStringList *outArgs,
+                                QString *outCmd, Arguments *outArgs, OsType osType,
                                 const Environment *env, const QString *pwd)
 {
     QtcProcess::SplitError err;
-    *outArgs = QtcProcess::prepareArgs(arguments, &err, env, pwd);
+    *outArgs = QtcProcess::prepareArgs(arguments, &err, osType, env, pwd);
     if (err == QtcProcess::SplitOk) {
         *outCmd = command;
     } else {
-        if (err != QtcProcess::FoundMeta)
-            return false;
-        *outCmd = QLatin1String("/bin/sh");
-        *outArgs << QLatin1String("-c") << (quoteArg(command) + QLatin1Char(' ') + arguments);
+        if (osType == OsTypeWindows) {
+            *outCmd = QString::fromLatin1(qgetenv("COMSPEC"));
+            *outArgs = Arguments::createWindowsArgs(QLatin1String("/v:off /s /c \"")
+                    + quoteArg(QDir::toNativeSeparators(command)) + QLatin1Char(' ') + arguments
+                    + QLatin1Char('"'));
+        } else {
+            if (err != QtcProcess::FoundMeta)
+                return false;
+            *outCmd = QLatin1String("/bin/sh");
+            *outArgs = Arguments::createUnixArgs(QStringList()
+                            << QLatin1String("-c")
+                            << (quoteArg(command) + QLatin1Char(' ') + arguments));
+        }
     }
     return true;
 }
-#endif
 
 void QtcProcess::start()
 {
     Environment env;
+    const OsType osType = HostOsInfo::hostOs();
     if (m_haveEnv) {
         if (m_environment.size() == 0)
             qWarning("QtcProcess::start: Empty environment set when running '%s'.", qPrintable(m_command));
@@ -655,16 +683,12 @@ void QtcProcess::start()
         // If the process environemnt has no libraryPath,
         // Qt will copy creator's libraryPath into the process environment.
         // That's brain dead, and we work around it
-#if defined(Q_OS_UNIX)
-#  if defined(Q_OS_MAC)
-        static const char libraryPathC[] = "DYLD_LIBRARY_PATH";
-#  else
-        static const char libraryPathC[] = "LD_LIBRARY_PATH";
-#  endif
-        const QString libraryPath = QLatin1String(libraryPathC);
-        if (env.constFind(libraryPath) == env.constEnd())
-            env.set(libraryPath, QString());
-#endif
+        if (osType != OsTypeWindows) {  // a.k.a "Unixoid"
+            const QString libraryPath =
+                QLatin1String(osType == OsTypeMac ? "DYLD_LIBRARY_PATH" : "LD_LIBRARY_PATH");
+            if (env.constFind(libraryPath) == env.constEnd())
+                env.set(libraryPath, QString());
+        }
         QProcess::setEnvironment(env.toStringList());
     } else {
         env = Environment::systemEnvironment();
@@ -672,27 +696,28 @@ void QtcProcess::start()
 
     const QString &workDir = workingDirectory();
     QString command;
+    QtcProcess::Arguments arguments;
+    bool success = prepareCommand(m_command, m_arguments, &command, &arguments, osType, &env, &workDir);
+    if (osType == OsTypeWindows) {
+        QString args = arguments.toWindowsArgs();
 #ifdef Q_OS_WIN
-    QString arguments;
-    QStringList argList;
-    prepareCommand(m_command, m_arguments, &command, &arguments, &env, &workDir);
-    setNativeArguments(arguments);
-    if (m_useCtrlCStub) {
-        argList << QDir::toNativeSeparators(command);
-        command = QCoreApplication::applicationDirPath() + QLatin1String("/qtcreator_ctrlc_stub.exe");
-    }
-    QProcess::start(command, argList);
-#else
-    QStringList arguments;
-    if (!prepareCommand(m_command, m_arguments, &command, &arguments, &env, &workDir)) {
-        setErrorString(tr("Error in command line."));
-        // Should be FailedToStart, but we cannot set the process error from the outside,
-        // so it would be inconsistent.
-        emit error(QProcess::UnknownError);
-        return;
-    }
-    QProcess::start(command, arguments);
+        setNativeArguments(args);
 #endif
+        if (m_useCtrlCStub) {
+            args = QDir::toNativeSeparators(command);
+            command = QCoreApplication::applicationDirPath() + QLatin1String("/qtcreator_ctrlc_stub.exe");
+        }
+        QProcess::start(command, QStringList(args));
+    } else {
+        if (!success) {
+            setErrorString(tr("Error in command line."));
+            // Should be FailedToStart, but we cannot set the process error from the outside,
+            // so it would be inconsistent.
+            emit error(QProcess::UnknownError);
+            return;
+        }
+        QProcess::start(command, arguments.toUnixArgs());
+    }
 }
 
 #ifdef Q_OS_WIN
@@ -738,10 +763,9 @@ void QtcProcess::interrupt()
 #endif
 }
 
-#ifdef Q_OS_WIN
 // This function assumes that the resulting string will be quoted.
 // That's irrelevant if it does not contain quotes itself.
-static int quoteArgInternal(QString &ret, int bslashes)
+static int quoteArgInternalWin(QString &ret, int bslashes)
 {
     // Quotes are escaped and their preceding backslashes are doubled.
     // It's impossible to escape anything inside a quoted string on cmd
@@ -765,32 +789,6 @@ static int quoteArgInternal(QString &ret, int bslashes)
     return bslashes;
 }
 
-#else
-
-// The main state of the Unix shell parser
-enum MxQuoting { MxBasic, MxSingleQuote, MxDoubleQuote, MxParen, MxSubst, MxGroup, MxMath };
-typedef struct {
-    MxQuoting current;
-    // Bizarrely enough, double quoting has an impact on the behavior of some
-    // complex expressions within the quoted string.
-    bool dquote;
-} MxState;
-QT_BEGIN_NAMESPACE
-Q_DECLARE_TYPEINFO(MxState, Q_PRIMITIVE_TYPE);
-QT_END_NAMESPACE
-
-// Pushed state for the case where a $(()) expansion turns out bogus
-typedef struct {
-    QString str;
-    int pos, varPos;
-} MxSave;
-QT_BEGIN_NAMESPACE
-Q_DECLARE_TYPEINFO(MxSave, Q_MOVABLE_TYPE);
-QT_END_NAMESPACE
-
-#include <QStack>
-
-#endif
 
 // TODO: This documentation is relevant for end-users. Where to put it?
 /**
@@ -864,7 +862,7 @@ QT_END_NAMESPACE
  * \return false if the string could not be parsed and therefore no safe
  *   substitution was possible
  */
-bool QtcProcess::expandMacros(QString *cmd, AbstractMacroExpander *mx)
+bool QtcProcess::expandMacros(QString *cmd, AbstractMacroExpander *mx, OsType osType)
 {
     QString str = *cmd;
     if (str.isEmpty())
@@ -878,319 +876,320 @@ bool QtcProcess::expandMacros(QString *cmd, AbstractMacroExpander *mx)
 
     int pos = 0;
 
-#ifdef Q_OS_WIN
-    enum { // cmd.exe parsing state
-        ShellBasic, // initial state
-        ShellQuoted, // double-quoted state => *no* other meta chars are interpreted
-        ShellEscaped // circumflex-escaped state => next char is not interpreted
-    } shellState = ShellBasic;
-    enum { // CommandLineToArgv() parsing state and some more
-        CrtBasic, // initial state
-        CrtNeedWord, // after empty expando; insert empty argument if whitespace follows
-        CrtInWord, // in non-whitespace
-        CrtClosed, // previous char closed the double-quoting
-        CrtHadQuote, // closed double-quoting after an expando
-        // The remaining two need to be numerically higher
-        CrtQuoted, // double-quoted state => spaces don't split tokens
-        CrtNeedQuote // expando opened quote; close if no expando follows
-    } crtState = CrtBasic;
-    int bslashes = 0; // previous chars were manual backslashes
-    int rbslashes = 0; // trailing backslashes in replacement
+    if (osType == OsTypeWindows) {
+        enum { // cmd.exe parsing state
+            ShellBasic, // initial state
+            ShellQuoted, // double-quoted state => *no* other meta chars are interpreted
+            ShellEscaped // circumflex-escaped state => next char is not interpreted
+        } shellState = ShellBasic;
+        enum { // CommandLineToArgv() parsing state and some more
+            CrtBasic, // initial state
+            CrtNeedWord, // after empty expando; insert empty argument if whitespace follows
+            CrtInWord, // in non-whitespace
+            CrtClosed, // previous char closed the double-quoting
+            CrtHadQuote, // closed double-quoting after an expando
+            // The remaining two need to be numerically higher
+            CrtQuoted, // double-quoted state => spaces don't split tokens
+            CrtNeedQuote // expando opened quote; close if no expando follows
+        } crtState = CrtBasic;
+        int bslashes = 0; // previous chars were manual backslashes
+        int rbslashes = 0; // trailing backslashes in replacement
 
-    forever {
-        if (pos == varPos) {
-            if (shellState == ShellEscaped)
-                return false; // Circumflex'd quoted expando would be Bad (TM).
-            if ((shellState == ShellQuoted) != (crtState == CrtQuoted))
-                return false; // CRT quoting out of sync with shell quoting. Ahoy to Redmond.
-            rbslashes += bslashes;
-            bslashes = 0;
-            if (crtState < CrtQuoted) {
-                if (rsts.isEmpty()) {
-                    if (crtState == CrtBasic) {
-                        // Outside any quoting and the string is empty, so put
-                        // a pair of quotes. Delaying that is just pedantry.
-                        crtState = CrtNeedWord;
-                    }
-                } else {
-                    if (hasSpecialChars(rsts)) {
-                        if (crtState == CrtClosed) {
-                            // Quoted expando right after closing quote. Can't do that.
-                            return false;
+        forever {
+            if (pos == varPos) {
+                if (shellState == ShellEscaped)
+                    return false; // Circumflex'd quoted expando would be Bad (TM).
+                if ((shellState == ShellQuoted) != (crtState == CrtQuoted))
+                    return false; // CRT quoting out of sync with shell quoting. Ahoy to Redmond.
+                rbslashes += bslashes;
+                bslashes = 0;
+                if (crtState < CrtQuoted) {
+                    if (rsts.isEmpty()) {
+                        if (crtState == CrtBasic) {
+                            // Outside any quoting and the string is empty, so put
+                            // a pair of quotes. Delaying that is just pedantry.
+                            crtState = CrtNeedWord;
                         }
-                        int tbslashes = quoteArgInternal(rsts, 0);
-                        rsts.prepend(QLatin1Char('"'));
-                        if (rbslashes)
-                            rsts.prepend(QString(rbslashes, QLatin1Char('\\')));
-                        crtState = CrtNeedQuote;
-                        rbslashes = tbslashes;
                     } else {
-                        crtState = CrtInWord; // We know that this string contains no spaces.
-                        // We know that this string contains no quotes,
-                        // so the function won't make a mess.
-                        rbslashes = quoteArgInternal(rsts, rbslashes);
+                        if (hasSpecialCharsWin(rsts)) {
+                            if (crtState == CrtClosed) {
+                                // Quoted expando right after closing quote. Can't do that.
+                                return false;
+                            }
+                            int tbslashes = quoteArgInternalWin(rsts, 0);
+                            rsts.prepend(QLatin1Char('"'));
+                            if (rbslashes)
+                                rsts.prepend(QString(rbslashes, QLatin1Char('\\')));
+                            crtState = CrtNeedQuote;
+                            rbslashes = tbslashes;
+                        } else {
+                            crtState = CrtInWord; // We know that this string contains no spaces.
+                            // We know that this string contains no quotes,
+                            // so the function won't make a mess.
+                            rbslashes = quoteArgInternalWin(rsts, rbslashes);
+                        }
                     }
+                } else {
+                    rbslashes = quoteArgInternalWin(rsts, rbslashes);
                 }
-            } else {
-                rbslashes = quoteArgInternal(rsts, rbslashes);
-            }
-            str.replace(pos, varLen, rsts);
-            pos += rsts.length();
-            varPos = pos;
-            if (!(varLen = mx->findMacro(str, &varPos, &rsts))) {
-                // Don't leave immediately, as we may be in CrtNeedWord state which could
-                // be still resolved, or we may have inserted trailing backslashes.
-                varPos = INT_MAX;
-            }
-            continue;
-        }
-        if (crtState == CrtNeedQuote) {
-            if (rbslashes) {
-                str.insert(pos, QString(rbslashes, QLatin1Char('\\')));
-                pos += rbslashes;
-                varPos += rbslashes;
-                rbslashes = 0;
+                str.replace(pos, varLen, rsts);
+                pos += rsts.length();
+                varPos = pos;
+                if (!(varLen = mx->findMacro(str, &varPos, &rsts))) {
+                    // Don't leave immediately, as we may be in CrtNeedWord state which could
+                    // be still resolved, or we may have inserted trailing backslashes.
+                    varPos = INT_MAX;
+                }
+                continue;
             }
-            str.insert(pos, QLatin1Char('"'));
-            pos++;
-            varPos++;
-            crtState = CrtHadQuote;
-        }
-        ushort cc = str.unicode()[pos].unicode();
-        if (shellState == ShellBasic && cc == '^') {
-            shellState = ShellEscaped;
-        } else {
-            if (!cc || cc == ' ' || cc == '\t') {
-                if (crtState < CrtQuoted) {
-                    if (crtState == CrtNeedWord) {
-                        str.insert(pos, QLatin1String("\"\""));
-                        pos += 2;
-                        varPos += 2;
-                    }
-                    crtState = CrtBasic;
+            if (crtState == CrtNeedQuote) {
+                if (rbslashes) {
+                    str.insert(pos, QString(rbslashes, QLatin1Char('\\')));
+                    pos += rbslashes;
+                    varPos += rbslashes;
+                    rbslashes = 0;
                 }
-                if (!cc)
-                    break;
-                bslashes = 0;
-                rbslashes = 0;
+                str.insert(pos, QLatin1Char('"'));
+                pos++;
+                varPos++;
+                crtState = CrtHadQuote;
+            }
+            ushort cc = str.unicode()[pos].unicode();
+            if (shellState == ShellBasic && cc == '^') {
+                shellState = ShellEscaped;
             } else {
-                if (cc == '\\') {
-                    bslashes++;
-                    if (crtState < CrtQuoted)
-                        crtState = CrtInWord;
-                } else {
-                    if (cc == '"') {
-                        if (shellState != ShellEscaped)
-                            shellState = (shellState == ShellQuoted) ? ShellBasic : ShellQuoted;
-                        if (rbslashes) {
-                            // Offset -1: skip possible circumflex. We have at least
-                            // one backslash, so a fixed offset is ok.
-                            str.insert(pos - 1, QString(rbslashes, QLatin1Char('\\')));
-                            pos += rbslashes;
-                            varPos += rbslashes;
+                if (!cc || cc == ' ' || cc == '\t') {
+                    if (crtState < CrtQuoted) {
+                        if (crtState == CrtNeedWord) {
+                            str.insert(pos, QLatin1String("\"\""));
+                            pos += 2;
+                            varPos += 2;
                         }
-                        if (!(bslashes & 1)) {
-                            // Even number of backslashes, so the quote is not escaped.
-                            switch (crtState) {
-                            case CrtQuoted:
-                                // Closing quote
-                                crtState = CrtClosed;
-                                break;
-                            case CrtClosed:
-                                // Two consecutive quotes make a literal quote - and
-                                // still close quoting. See QtcProcess::quoteArg().
+                        crtState = CrtBasic;
+                    }
+                    if (!cc)
+                        break;
+                    bslashes = 0;
+                    rbslashes = 0;
+                } else {
+                    if (cc == '\\') {
+                        bslashes++;
+                        if (crtState < CrtQuoted)
+                            crtState = CrtInWord;
+                    } else {
+                        if (cc == '"') {
+                            if (shellState != ShellEscaped)
+                                shellState = (shellState == ShellQuoted) ? ShellBasic : ShellQuoted;
+                            if (rbslashes) {
+                                // Offset -1: skip possible circumflex. We have at least
+                                // one backslash, so a fixed offset is ok.
+                                str.insert(pos - 1, QString(rbslashes, QLatin1Char('\\')));
+                                pos += rbslashes;
+                                varPos += rbslashes;
+                            }
+                            if (!(bslashes & 1)) {
+                                // Even number of backslashes, so the quote is not escaped.
+                                switch (crtState) {
+                                case CrtQuoted:
+                                    // Closing quote
+                                    crtState = CrtClosed;
+                                    break;
+                                case CrtClosed:
+                                    // Two consecutive quotes make a literal quote - and
+                                    // still close quoting. See QtcProcess::quoteArg().
+                                    crtState = CrtInWord;
+                                    break;
+                                case CrtHadQuote:
+                                    // Opening quote right after quoted expando. Can't do that.
+                                    return false;
+                                default:
+                                    // Opening quote
+                                    crtState = CrtQuoted;
+                                    break;
+                                }
+                            } else if (crtState < CrtQuoted) {
                                 crtState = CrtInWord;
-                                break;
-                            case CrtHadQuote:
-                                // Opening quote right after quoted expando. Can't do that.
-                                return false;
-                            default:
-                                // Opening quote
-                                crtState = CrtQuoted;
-                                break;
                             }
                         } else if (crtState < CrtQuoted) {
                             crtState = CrtInWord;
                         }
-                    } else if (crtState < CrtQuoted) {
-                        crtState = CrtInWord;
+                        bslashes = 0;
+                        rbslashes = 0;
                     }
-                    bslashes = 0;
-                    rbslashes = 0;
                 }
+                if (varPos == INT_MAX && !rbslashes)
+                    break;
+                if (shellState == ShellEscaped)
+                    shellState = ShellBasic;
             }
-            if (varPos == INT_MAX && !rbslashes)
-                break;
-            if (shellState == ShellEscaped)
-                shellState = ShellBasic;
-        }
-        pos++;
-    }
-#else
-    MxState state = { MxBasic, false };
-    QStack<MxState> sstack;
-    QStack<MxSave> ostack;
-
-    while (pos < str.length()) {
-        if (pos == varPos) {
-            // Our expansion rules trigger in any context
-            if (state.dquote) {
-                // We are within a double-quoted string. Escape relevant meta characters.
-                rsts.replace(QRegExp(QLatin1String("([$`\"\\\\])")), QLatin1String("\\\\1"));
-            } else if (state.current == MxSingleQuote) {
-                // We are within a single-quoted string. "Suspend" single-quoting and put a
-                // single escaped quote for each single quote inside the string.
-                rsts.replace(QLatin1Char('\''), QLatin1String("'\\''"));
-            } else if (rsts.isEmpty() || hasSpecialCharsUnix(rsts)) {
-                // String contains "quote-worthy" characters. Use single quoting - but
-                // that choice is arbitrary.
-                rsts.replace(QLatin1Char('\''), QLatin1String("'\\''"));
-                rsts.prepend(QLatin1Char('\''));
-                rsts.append(QLatin1Char('\''));
-            } // Else just use the string verbatim.
-            str.replace(pos, varLen, rsts);
-            pos += rsts.length();
-            varPos = pos;
-            if (!(varLen = mx->findMacro(str, &varPos, &rsts)))
-                break;
-            continue;
+            pos++;
         }
-        ushort cc = str.unicode()[pos].unicode();
-        if (state.current == MxSingleQuote) {
-            // Single quoted context - only the single quote has any special meaning.
-            if (cc == '\'')
-                state = sstack.pop();
-        } else if (cc == '\\') {
-            // In any other context, the backslash starts an escape.
-            pos += 2;
-            if (varPos < pos)
-                return false; // Backslash'd quoted expando would be Bad (TM).
-            continue;
-        } else if (cc == '$') {
-            cc = str.unicode()[++pos].unicode();
-            if (cc == '(') {
-                sstack.push(state);
-                if (str.unicode()[pos + 1].unicode() == '(') {
-                    // $(( starts a math expression. This may also be a $( ( in fact,
-                    // so we push the current string and offset on a stack so we can retry.
-                    MxSave sav = { str, pos + 2, varPos };
-                    ostack.push(sav);
-                    state.current = MxMath;
-                    pos += 2;
-                    continue;
-                } else {
-                    // $( starts a command substitution. This actually "opens a new context"
-                    // which overrides surrounding double quoting.
-                    state.current = MxParen;
-                    state.dquote = false;
-                }
-            } else if (cc == '{') {
-                // ${ starts a "braced" variable substitution.
-                sstack.push(state);
-                state.current = MxSubst;
-            } // Else assume that a "bare" variable substitution has started
-        } else if (cc == '`') {
-            // Backticks are evil, as every shell interprets escapes within them differently,
-            // which is a danger for the quoting of our own expansions.
-            // So we just apply *our* rules (which match bash) and transform it into a POSIX
-            // command substitution which has clear semantics.
-            str.replace(pos, 1, QLatin1String("$( " )); // add space -> avoid creating $((
-            varPos += 2;
-            int pos2 = pos += 3;
-            forever {
-                if (pos2 >= str.length())
-                    return false; // Syntax error - unterminated backtick expression.
-                cc = str.unicode()[pos2].unicode();
-                if (cc == '`')
+    } else {
+        // !Windows
+        MxState state = { MxBasic, false };
+        QStack<MxState> sstack;
+        QStack<MxSave> ostack;
+
+        while (pos < str.length()) {
+            if (pos == varPos) {
+                // Our expansion rules trigger in any context
+                if (state.dquote) {
+                    // We are within a double-quoted string. Escape relevant meta characters.
+                    rsts.replace(QRegExp(QLatin1String("([$`\"\\\\])")), QLatin1String("\\\\1"));
+                } else if (state.current == MxSingleQuote) {
+                    // We are within a single-quoted string. "Suspend" single-quoting and put a
+                    // single escaped quote for each single quote inside the string.
+                    rsts.replace(QLatin1Char('\''), QLatin1String("'\\''"));
+                } else if (rsts.isEmpty() || hasSpecialCharsUnix(rsts)) {
+                    // String contains "quote-worthy" characters. Use single quoting - but
+                    // that choice is arbitrary.
+                    rsts.replace(QLatin1Char('\''), QLatin1String("'\\''"));
+                    rsts.prepend(QLatin1Char('\''));
+                    rsts.append(QLatin1Char('\''));
+                } // Else just use the string verbatim.
+                str.replace(pos, varLen, rsts);
+                pos += rsts.length();
+                varPos = pos;
+                if (!(varLen = mx->findMacro(str, &varPos, &rsts)))
                     break;
-                if (cc == '\\') {
-                    cc = str.unicode()[++pos2].unicode();
-                    if (cc == '$' || cc == '`' || cc == '\\' ||
-                        (cc == '"' && state.dquote))
-                    {
-                        str.remove(pos2 - 1, 1);
-                        if (varPos >= pos2)
-                            varPos--;
+                continue;
+            }
+            ushort cc = str.unicode()[pos].unicode();
+            if (state.current == MxSingleQuote) {
+                // Single quoted context - only the single quote has any special meaning.
+                if (cc == '\'')
+                    state = sstack.pop();
+            } else if (cc == '\\') {
+                // In any other context, the backslash starts an escape.
+                pos += 2;
+                if (varPos < pos)
+                    return false; // Backslash'd quoted expando would be Bad (TM).
+                continue;
+            } else if (cc == '$') {
+                cc = str.unicode()[++pos].unicode();
+                if (cc == '(') {
+                    sstack.push(state);
+                    if (str.unicode()[pos + 1].unicode() == '(') {
+                        // $(( starts a math expression. This may also be a $( ( in fact,
+                        // so we push the current string and offset on a stack so we can retry.
+                        MxSave sav = { str, pos + 2, varPos };
+                        ostack.push(sav);
+                        state.current = MxMath;
+                        pos += 2;
                         continue;
+                    } else {
+                        // $( starts a command substitution. This actually "opens a new context"
+                        // which overrides surrounding double quoting.
+                        state.current = MxParen;
+                        state.dquote = false;
+                    }
+                } else if (cc == '{') {
+                    // ${ starts a "braced" variable substitution.
+                    sstack.push(state);
+                    state.current = MxSubst;
+                } // Else assume that a "bare" variable substitution has started
+            } else if (cc == '`') {
+                // Backticks are evil, as every shell interprets escapes within them differently,
+                // which is a danger for the quoting of our own expansions.
+                // So we just apply *our* rules (which match bash) and transform it into a POSIX
+                // command substitution which has clear semantics.
+                str.replace(pos, 1, QLatin1String("$( " )); // add space -> avoid creating $((
+                varPos += 2;
+                int pos2 = pos += 3;
+                forever {
+                    if (pos2 >= str.length())
+                        return false; // Syntax error - unterminated backtick expression.
+                    cc = str.unicode()[pos2].unicode();
+                    if (cc == '`')
+                        break;
+                    if (cc == '\\') {
+                        cc = str.unicode()[++pos2].unicode();
+                        if (cc == '$' || cc == '`' || cc == '\\' ||
+                            (cc == '"' && state.dquote))
+                        {
+                            str.remove(pos2 - 1, 1);
+                            if (varPos >= pos2)
+                                varPos--;
+                            continue;
+                        }
                     }
+                    pos2++;
                 }
-                pos2++;
-            }
-            str[pos2] = QLatin1Char(')');
-            sstack.push(state);
-            state.current = MxParen;
-            state.dquote = false;
-            continue;
-        } else if (state.current == MxDoubleQuote) {
-            // (Truly) double quoted context - only remaining special char is the closing quote.
-            if (cc == '"')
-                state = sstack.pop();
-        } else if (cc == '\'') {
-            // Start single quote if we are not in "inherited" double quoted context.
-            if (!state.dquote) {
-                sstack.push(state);
-                state.current = MxSingleQuote;
-            }
-        } else if (cc == '"') {
-            // Same for double quoting.
-            if (!state.dquote) {
+                str[pos2] = QLatin1Char(')');
                 sstack.push(state);
-                state.current = MxDoubleQuote;
-                state.dquote = true;
-            }
-        } else if (state.current == MxSubst) {
-            // "Braced" substitution context - only remaining special char is the closing brace.
-            if (cc == '}')
-                state = sstack.pop();
-        } else if (cc == ')') {
-            if (state.current == MxMath) {
-                if (str.unicode()[pos + 1].unicode() == ')') {
+                state.current = MxParen;
+                state.dquote = false;
+                continue;
+            } else if (state.current == MxDoubleQuote) {
+                // (Truly) double quoted context - only remaining special char is the closing quote.
+                if (cc == '"')
                     state = sstack.pop();
-                    pos += 2;
-                } else {
-                    // False hit: the $(( was a $( ( in fact.
-                    // ash does not care (and will complain), but bash actually parses it.
-                    varPos = ostack.top().varPos;
-                    pos = ostack.top().pos;
-                    str = ostack.top().str;
-                    ostack.pop();
-                    state.current = MxParen;
-                    state.dquote = false;
+            } else if (cc == '\'') {
+                // Start single quote if we are not in "inherited" double quoted context.
+                if (!state.dquote) {
                     sstack.push(state);
+                    state.current = MxSingleQuote;
                 }
-                continue;
-            } else if (state.current == MxParen) {
-                state = sstack.pop();
-            } else {
-                break; // Syntax error - excess closing parenthesis.
+            } else if (cc == '"') {
+                // Same for double quoting.
+                if (!state.dquote) {
+                    sstack.push(state);
+                    state.current = MxDoubleQuote;
+                    state.dquote = true;
+                }
+            } else if (state.current == MxSubst) {
+                // "Braced" substitution context - only remaining special char is the closing brace.
+                if (cc == '}')
+                    state = sstack.pop();
+            } else if (cc == ')') {
+                if (state.current == MxMath) {
+                    if (str.unicode()[pos + 1].unicode() == ')') {
+                        state = sstack.pop();
+                        pos += 2;
+                    } else {
+                        // False hit: the $(( was a $( ( in fact.
+                        // ash does not care (and will complain), but bash actually parses it.
+                        varPos = ostack.top().varPos;
+                        pos = ostack.top().pos;
+                        str = ostack.top().str;
+                        ostack.pop();
+                        state.current = MxParen;
+                        state.dquote = false;
+                        sstack.push(state);
+                    }
+                    continue;
+                } else if (state.current == MxParen) {
+                    state = sstack.pop();
+                } else {
+                    break; // Syntax error - excess closing parenthesis.
+                }
+            } else if (cc == '}') {
+                if (state.current == MxGroup)
+                    state = sstack.pop();
+                else
+                    break; // Syntax error - excess closing brace.
+            } else if (cc == '(') {
+                // Context-saving command grouping.
+                sstack.push(state);
+                state.current = MxParen;
+            } else if (cc == '{') {
+                // Plain command grouping.
+                sstack.push(state);
+                state.current = MxGroup;
             }
-        } else if (cc == '}') {
-            if (state.current == MxGroup)
-                state = sstack.pop();
-            else
-                break; // Syntax error - excess closing brace.
-        } else if (cc == '(') {
-            // Context-saving command grouping.
-            sstack.push(state);
-            state.current = MxParen;
-        } else if (cc == '{') {
-            // Plain command grouping.
-            sstack.push(state);
-            state.current = MxGroup;
+            pos++;
         }
-        pos++;
+        // FIXME? May complain if (!sstack.empty()), but we don't really care anyway.
     }
-    // FIXME? May complain if (!sstack.empty()), but we don't really care anyway.
-#endif
 
     *cmd = str;
     return true;
 }
 
-QString QtcProcess::expandMacros(const QString &str, AbstractMacroExpander *mx)
+QString QtcProcess::expandMacros(const QString &str, AbstractMacroExpander *mx, OsType osType)
 {
     QString ret = str;
-    expandMacros(&ret, mx);
+    expandMacros(&ret, mx, osType);
     return ret;
 }
 
@@ -1203,252 +1202,252 @@ bool QtcProcess::ArgIterator::next()
     m_simple = true;
     m_value.clear();
 
-#ifdef Q_OS_WIN
-    enum { // cmd.exe parsing state
-        ShellBasic, // initial state
-        ShellQuoted, // double-quoted state => *no* other meta chars are interpreted
-        ShellEscaped // circumflex-escaped state => next char is not interpreted
-    } shellState = ShellBasic;
-    enum { // CommandLineToArgv() parsing state and some more
-        CrtBasic, // initial state
-        CrtInWord, // in non-whitespace
-        CrtClosed, // previous char closed the double-quoting
-        CrtQuoted // double-quoted state => spaces don't split tokens
-    } crtState = CrtBasic;
-    enum { NoVar, NewVar, FullVar } varState = NoVar; // inside a potential env variable expansion
-    int bslashes = 0; // number of preceding backslashes
-
-    for (;; m_pos++) {
-        ushort cc = m_pos < m_str->length() ? m_str->unicode()[m_pos].unicode() : 0;
-        if (shellState == ShellBasic && cc == '^') {
-            varState = NoVar;
-            shellState = ShellEscaped;
-        } else if ((shellState == ShellBasic && isMetaChar(cc)) || !cc) { // A "bit" simplistic ...
-            // We ignore crtQuote state here. Whatever ...
-          doReturn:
-            if (m_simple)
-                while (--bslashes >= 0)
-                    m_value += QLatin1Char('\\');
-            else
-                m_value.clear();
-            if (crtState != CrtBasic) {
-                m_prev = prev;
-                return true;
-            }
-            return false;
-        } else {
-            if (crtState != CrtQuoted && (cc == ' ' || cc == '\t')) {
+    if (m_osType == OsTypeWindows) {
+        enum { // cmd.exe parsing state
+            ShellBasic, // initial state
+            ShellQuoted, // double-quoted state => *no* other meta chars are interpreted
+            ShellEscaped // circumflex-escaped state => next char is not interpreted
+        } shellState = ShellBasic;
+        enum { // CommandLineToArgv() parsing state and some more
+            CrtBasic, // initial state
+            CrtInWord, // in non-whitespace
+            CrtClosed, // previous char closed the double-quoting
+            CrtQuoted // double-quoted state => spaces don't split tokens
+        } crtState = CrtBasic;
+        enum { NoVar, NewVar, FullVar } varState = NoVar; // inside a potential env variable expansion
+        int bslashes = 0; // number of preceding backslashes
+
+        for (;; m_pos++) {
+            ushort cc = m_pos < m_str->length() ? m_str->unicode()[m_pos].unicode() : 0;
+            if (shellState == ShellBasic && cc == '^') {
+                varState = NoVar;
+                shellState = ShellEscaped;
+            } else if ((shellState == ShellBasic && isMetaCharWin(cc)) || !cc) { // A "bit" simplistic ...
+                // We ignore crtQuote state here. Whatever ...
+              doReturn:
+                if (m_simple)
+                    while (--bslashes >= 0)
+                        m_value += QLatin1Char('\\');
+                else
+                    m_value.clear();
                 if (crtState != CrtBasic) {
-                    // We'll lose shellQuote state here. Whatever ...
-                    goto doReturn;
+                    m_prev = prev;
+                    return true;
                 }
+                return false;
             } else {
-                if (cc == '\\') {
-                    bslashes++;
-                    if (crtState != CrtQuoted)
-                        crtState = CrtInWord;
-                    varState = NoVar;
+                if (crtState != CrtQuoted && (cc == ' ' || cc == '\t')) {
+                    if (crtState != CrtBasic) {
+                        // We'll lose shellQuote state here. Whatever ...
+                        goto doReturn;
+                    }
                 } else {
-                    if (cc == '"') {
-                        varState = NoVar;
-                        if (shellState != ShellEscaped)
-                            shellState = (shellState == ShellQuoted) ? ShellBasic : ShellQuoted;
-                        int obslashes = bslashes;
-                        bslashes >>= 1;
-                        if (!(obslashes & 1)) {
-                            // Even number of backslashes, so the quote is not escaped.
-                            switch (crtState) {
-                            case CrtQuoted:
-                                // Closing quote
-                                crtState = CrtClosed;
-                                continue;
-                            case CrtClosed:
-                                // Two consecutive quotes make a literal quote - and
-                                // still close quoting. See quoteArg().
-                                crtState = CrtInWord;
-                                break;
-                            default:
-                                // Opening quote
-                                crtState = CrtQuoted;
-                                continue;
-                            }
-                        } else if (crtState != CrtQuoted) {
+                    if (cc == '\\') {
+                        bslashes++;
+                        if (crtState != CrtQuoted)
                             crtState = CrtInWord;
-                        }
+                        varState = NoVar;
                     } else {
-                        if (cc == '%') {
-                            if (varState == FullVar) {
-                                m_simple = false;
-                                varState = NoVar;
-                            } else {
-                                varState = NewVar;
+                        if (cc == '"') {
+                            varState = NoVar;
+                            if (shellState != ShellEscaped)
+                                shellState = (shellState == ShellQuoted) ? ShellBasic : ShellQuoted;
+                            int obslashes = bslashes;
+                            bslashes >>= 1;
+                            if (!(obslashes & 1)) {
+                                // Even number of backslashes, so the quote is not escaped.
+                                switch (crtState) {
+                                case CrtQuoted:
+                                    // Closing quote
+                                    crtState = CrtClosed;
+                                    continue;
+                                case CrtClosed:
+                                    // Two consecutive quotes make a literal quote - and
+                                    // still close quoting. See quoteArg().
+                                    crtState = CrtInWord;
+                                    break;
+                                default:
+                                    // Opening quote
+                                    crtState = CrtQuoted;
+                                    continue;
+                                }
+                            } else if (crtState != CrtQuoted) {
+                                crtState = CrtInWord;
                             }
-                        } else if (varState != NoVar) {
-                            // This check doesn't really reflect cmd reality, but it is an
-                            // approximation of what would be sane.
-                            varState = (cc == '_' || cc == '-' || cc == '.'
-                                     || QChar(cc).isLetterOrNumber()) ? FullVar : NoVar;
+                        } else {
+                            if (cc == '%') {
+                                if (varState == FullVar) {
+                                    m_simple = false;
+                                    varState = NoVar;
+                                } else {
+                                    varState = NewVar;
+                                }
+                            } else if (varState != NoVar) {
+                                // This check doesn't really reflect cmd reality, but it is an
+                                // approximation of what would be sane.
+                                varState = (cc == '_' || cc == '-' || cc == '.'
+                                         || QChar(cc).isLetterOrNumber()) ? FullVar : NoVar;
 
+                            }
+                            if (crtState != CrtQuoted)
+                                crtState = CrtInWord;
                         }
-                        if (crtState != CrtQuoted)
-                            crtState = CrtInWord;
+                        for (; bslashes > 0; bslashes--)
+                            m_value += QLatin1Char('\\');
+                        m_value += QChar(cc);
                     }
-                    for (; bslashes > 0; bslashes--)
-                        m_value += QLatin1Char('\\');
-                    m_value += QChar(cc);
                 }
+                if (shellState == ShellEscaped)
+                    shellState = ShellBasic;
             }
-            if (shellState == ShellEscaped)
-                shellState = ShellBasic;
         }
-    }
-#else
-    MxState state = { MxBasic, false };
-    QStack<MxState> sstack;
-    QStack<int> ostack;
-    bool hadWord = false;
-
-    for (; m_pos < m_str->length(); m_pos++) {
-        ushort cc = m_str->unicode()[m_pos].unicode();
-        if (state.current == MxSingleQuote) {
-            if (cc == '\'') {
-                state = sstack.pop();
-                continue;
-            }
-        } else if (cc == '\\') {
-            if (++m_pos >= m_str->length())
-                break;
-            cc = m_str->unicode()[m_pos].unicode();
-            if (state.dquote && cc != '"' && cc != '\\' && cc != '$' && cc != '`')
-                m_value += QLatin1Char('\\');
-        } else if (cc == '$') {
-            if (++m_pos >= m_str->length())
-                break;
-            cc = m_str->unicode()[m_pos].unicode();
-            if (cc == '(') {
-                sstack.push(state);
+    } else {
+        MxState state = { MxBasic, false };
+        QStack<MxState> sstack;
+        QStack<int> ostack;
+        bool hadWord = false;
+
+        for (; m_pos < m_str->length(); m_pos++) {
+            ushort cc = m_str->unicode()[m_pos].unicode();
+            if (state.current == MxSingleQuote) {
+                if (cc == '\'') {
+                    state = sstack.pop();
+                    continue;
+                }
+            } else if (cc == '\\') {
+                if (++m_pos >= m_str->length())
+                    break;
+                cc = m_str->unicode()[m_pos].unicode();
+                if (state.dquote && cc != '"' && cc != '\\' && cc != '$' && cc != '`')
+                    m_value += QLatin1Char('\\');
+            } else if (cc == '$') {
                 if (++m_pos >= m_str->length())
                     break;
-                if (m_str->unicode()[m_pos].unicode() == '(') {
-                    ostack.push(m_pos);
-                    state.current = MxMath;
+                cc = m_str->unicode()[m_pos].unicode();
+                if (cc == '(') {
+                    sstack.push(state);
+                    if (++m_pos >= m_str->length())
+                        break;
+                    if (m_str->unicode()[m_pos].unicode() == '(') {
+                        ostack.push(m_pos);
+                        state.current = MxMath;
+                    } else {
+                        state.dquote = false;
+                        state.current = MxParen;
+                        // m_pos too far by one now - whatever.
+                    }
+                } else if (cc == '{') {
+                    sstack.push(state);
+                    state.current = MxSubst;
                 } else {
-                    state.dquote = false;
-                    state.current = MxParen;
                     // m_pos too far by one now - whatever.
                 }
-            } else if (cc == '{') {
-                sstack.push(state);
-                state.current = MxSubst;
-            } else {
-                // m_pos too far by one now - whatever.
-            }
-            m_simple = false;
-            hadWord = true;
-            continue;
-        } else if (cc == '`') {
-            forever {
-                if (++m_pos >= m_str->length()) {
-                    m_simple = false;
-                    m_prev = prev;
-                    return true;
-                }
-                cc = m_str->unicode()[m_pos].unicode();
-                if (cc == '`')
-                    break;
-                if (cc == '\\')
-                    m_pos++; // m_pos may be too far by one now - whatever.
-            }
-            m_simple = false;
-            hadWord = true;
-            continue;
-        } else if (state.current == MxDoubleQuote) {
-            if (cc == '"') {
-                state = sstack.pop();
-                continue;
-            }
-        } else if (cc == '\'') {
-            if (!state.dquote) {
-                sstack.push(state);
-                state.current = MxSingleQuote;
+                m_simple = false;
                 hadWord = true;
                 continue;
-            }
-        } else if (cc == '"') {
-            if (!state.dquote) {
-                sstack.push(state);
-                state.dquote = true;
-                state.current = MxDoubleQuote;
+            } else if (cc == '`') {
+                forever {
+                    if (++m_pos >= m_str->length()) {
+                        m_simple = false;
+                        m_prev = prev;
+                        return true;
+                    }
+                    cc = m_str->unicode()[m_pos].unicode();
+                    if (cc == '`')
+                        break;
+                    if (cc == '\\')
+                        m_pos++; // m_pos may be too far by one now - whatever.
+                }
+                m_simple = false;
                 hadWord = true;
                 continue;
-            }
-        } else if (state.current == MxSubst) {
-            if (cc == '}')
-                state = sstack.pop();
-            continue; // Not simple anyway
-        } else if (cc == ')') {
-            if (state.current == MxMath) {
-                if (++m_pos >= m_str->length())
-                    break;
-                if (m_str->unicode()[m_pos].unicode() == ')') {
-                    ostack.pop();
+            } else if (state.current == MxDoubleQuote) {
+                if (cc == '"') {
                     state = sstack.pop();
-                } else {
-                    // false hit: the $(( was a $( ( in fact.
-                    // ash does not care, but bash does.
-                    m_pos = ostack.pop();
-                    state.current = MxParen;
-                    state.dquote = false;
+                    continue;
+                }
+            } else if (cc == '\'') {
+                if (!state.dquote) {
                     sstack.push(state);
+                    state.current = MxSingleQuote;
+                    hadWord = true;
+                    continue;
+                }
+            } else if (cc == '"') {
+                if (!state.dquote) {
+                    sstack.push(state);
+                    state.dquote = true;
+                    state.current = MxDoubleQuote;
+                    hadWord = true;
+                    continue;
+                }
+            } else if (state.current == MxSubst) {
+                if (cc == '}')
+                    state = sstack.pop();
+                continue; // Not simple anyway
+            } else if (cc == ')') {
+                if (state.current == MxMath) {
+                    if (++m_pos >= m_str->length())
+                        break;
+                    if (m_str->unicode()[m_pos].unicode() == ')') {
+                        ostack.pop();
+                        state = sstack.pop();
+                    } else {
+                        // false hit: the $(( was a $( ( in fact.
+                        // ash does not care, but bash does.
+                        m_pos = ostack.pop();
+                        state.current = MxParen;
+                        state.dquote = false;
+                        sstack.push(state);
+                    }
+                    continue;
+                } else if (state.current == MxParen) {
+                    state = sstack.pop();
+                    continue;
+                } else {
+                    break;
                 }
-                continue;
-            } else if (state.current == MxParen) {
-                state = sstack.pop();
-                continue;
-            } else {
-                break;
-            }
 #if 0 // MxGroup is impossible, see below.
-        } else if (cc == '}') {
-            if (state.current == MxGroup) {
-                state = sstack.pop();
-                continue;
-            }
+            } else if (cc == '}') {
+                if (state.current == MxGroup) {
+                    state = sstack.pop();
+                    continue;
+                }
 #endif
-        } else if (cc == '(') {
-            sstack.push(state);
-            state.current = MxParen;
-            m_simple = false;
-            hadWord = true;
+            } else if (cc == '(') {
+                sstack.push(state);
+                state.current = MxParen;
+                m_simple = false;
+                hadWord = true;
 #if 0 // Should match only at the beginning of a command, which we never have currently.
-        } else if (cc == '{') {
-            sstack.push(state);
-            state.current = MxGroup;
-            m_simple = false;
-            hadWord = true;
-            continue;
-#endif
-        } else if (cc == '<' || cc == '>' || cc == '&' || cc == '|' || cc == ';') {
-            if (sstack.isEmpty())
-                break;
-        } else if (cc == ' ' || cc == '\t') {
-            if (!hadWord)
+            } else if (cc == '{') {
+                sstack.push(state);
+                state.current = MxGroup;
+                m_simple = false;
+                hadWord = true;
                 continue;
-            if (sstack.isEmpty())
-                break;
+#endif
+            } else if (cc == '<' || cc == '>' || cc == '&' || cc == '|' || cc == ';') {
+                if (sstack.isEmpty())
+                    break;
+            } else if (cc == ' ' || cc == '\t') {
+                if (!hadWord)
+                    continue;
+                if (sstack.isEmpty())
+                    break;
+            }
+            m_value += QChar(cc);
+            hadWord = true;
         }
-        m_value += QChar(cc);
-        hadWord = true;
-    }
-    // TODO: Possibly complain here if (!sstack.empty())
-    if (!m_simple)
-        m_value.clear();
-    if (hadWord) {
-        m_prev = prev;
-        return true;
+        // TODO: Possibly complain here if (!sstack.empty())
+        if (!m_simple)
+            m_value.clear();
+        if (hadWord) {
+            m_prev = prev;
+            return true;
+        }
+        return false;
     }
-    return false;
-#endif
 }
 
 void QtcProcess::ArgIterator::deleteArg()
@@ -1470,8 +1469,6 @@ void QtcProcess::ArgIterator::appendArg(const QString &str)
     m_pos += qstr.length() + 1;
 }
 
-namespace Utils {
-
 QTCREATOR_UTILS_EXPORT unsigned long qPidToPid(const Q_PID qpid)
 {
 #ifdef Q_OS_WIN
@@ -1482,4 +1479,40 @@ QTCREATOR_UTILS_EXPORT unsigned long qPidToPid(const Q_PID qpid)
 #endif
 }
 
+QtcProcess::Arguments QtcProcess::Arguments::createWindowsArgs(const QString &args)
+{
+    Arguments result;
+    result.m_windowsArgs = args;
+    result.m_isWindows = true;
+    return result;
+}
+
+QtcProcess::Arguments QtcProcess::Arguments::createUnixArgs(const QStringList &args)
+{
+    Arguments result;
+    result.m_unixArgs = args;
+    result.m_isWindows = false;
+    return result;
+}
+
+QString QtcProcess::Arguments::toWindowsArgs() const
+{
+    QTC_CHECK(m_isWindows);
+    return m_windowsArgs;
+}
+
+QStringList QtcProcess::Arguments::toUnixArgs() const
+{
+    QTC_CHECK(!m_isWindows);
+    return m_unixArgs;
+}
+
+QString QtcProcess::Arguments::toString() const
+{
+    if (m_isWindows)
+        return m_windowsArgs;
+    else
+        return QtcProcess::joinArgs(m_unixArgs, OsTypeLinux);
+}
+
 } // namespace Utils
diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h
index 66cbcbda5b..ca1dff19fc 100644
--- a/src/libs/utils/qtcprocess.h
+++ b/src/libs/utils/qtcprocess.h
@@ -56,6 +56,22 @@ public:
     void terminate();
     void interrupt();
 
+    class QTCREATOR_UTILS_EXPORT Arguments
+    {
+    public:
+        static Arguments createWindowsArgs(const QString &args);
+        static Arguments createUnixArgs(const QStringList &args);
+
+        QString toWindowsArgs() const;
+        QStringList toUnixArgs() const;
+        QString toString() const;
+
+    private:
+        QString m_windowsArgs;
+        QStringList m_unixArgs;
+        bool m_isWindows;
+    };
+
     enum SplitError {
         SplitOk = 0, //! All went just fine
         BadQuoting, //! Command contains quoting errors
@@ -64,45 +80,33 @@ public:
 
     //! Quote a single argument for usage in a unix shell command
     static QString quoteArgUnix(const QString &arg);
-    //! Quote a single argument and append it to a unix shell command
-    static void addArgUnix(QString *args, const QString &arg);
-    //! Join an argument list into a unix shell command
-    static QString joinArgsUnix(const QStringList &args);
-#ifdef Q_OS_WIN
     //! Quote a single argument for usage in a shell command
-    static QString quoteArg(const QString &arg);
+    static QString quoteArg(const QString &arg, OsType osType = HostOsInfo::hostOs());
     //! Quote a single argument and append it to a shell command
-    static void addArg(QString *args, const QString &arg);
+    static void addArg(QString *args, const QString &arg, OsType osType = HostOsInfo::hostOs());
     //! Join an argument list into a shell command
-    static QString joinArgs(const QStringList &args);
+    static QString joinArgs(const QStringList &args, OsType osType = HostOsInfo::hostOs());
     //! Prepare argument of a shell command for feeding into QProcess
-    static QString prepareArgs(const QString &cmd, SplitError *err,
-                               const Environment *env = 0, const QString *pwd = 0);
+    static Arguments prepareArgs(const QString &cmd, SplitError *err,
+                                 OsType osType = HostOsInfo::hostOs(),
+                                 const Environment *env = 0, const QString *pwd = 0);
     //! Prepare a shell command for feeding into QProcess
-    static void prepareCommand(const QString &command, const QString &arguments,
-                               QString *outCmd, QString *outArgs,
-                               const Environment *env = 0, const QString *pwd = 0);
-#else
-    static QString quoteArg(const QString &arg) { return quoteArgUnix(arg); }
-    static void addArg(QString *args, const QString &arg) { addArgUnix(args, arg); }
-    static QString joinArgs(const QStringList &args) { return joinArgsUnix(args); }
-    static QStringList prepareArgs(const QString &cmd, SplitError *err,
-                                   const Environment *env = 0, const QString *pwd = 0)
-        { return splitArgs(cmd, true, err, env, pwd); }
     static bool prepareCommand(const QString &command, const QString &arguments,
-                               QString *outCmd, QStringList *outArgs,
+                               QString *outCmd, Arguments *outArgs, OsType osType = HostOsInfo::hostOs(),
                                const Environment *env = 0, const QString *pwd = 0);
-#endif
     //! Quote and append each argument to a shell command
     static void addArgs(QString *args, const QStringList &inArgs);
     //! Append already quoted arguments to a shell command
     static void addArgs(QString *args, const QString &inArgs);
     //! Split a shell command into separate arguments. ArgIterator is usually a better choice.
-    static QStringList splitArgs(const QString &cmd, bool abortOnMeta = false, SplitError *err = 0,
+    static QStringList splitArgs(const QString &cmd, OsType osType = HostOsInfo::hostOs(),
+                                 bool abortOnMeta = false, SplitError *err = 0,
                                  const Environment *env = 0, const QString *pwd = 0);
     //! Safely replace the expandos in a shell command
-    static bool expandMacros(QString *cmd, AbstractMacroExpander *mx);
-    static QString expandMacros(const QString &str, AbstractMacroExpander *mx);
+    static bool expandMacros(QString *cmd, AbstractMacroExpander *mx,
+                             OsType osType = HostOsInfo::hostOs());
+    static QString expandMacros(const QString &str, AbstractMacroExpander *mx,
+                                OsType osType = HostOsInfo::hostOs());
 
     /*! Iterate over arguments from a command line.
      *  Assumes that the name of the actual command is *not* part of the line.
@@ -110,7 +114,9 @@ public:
      */
     class QTCREATOR_UTILS_EXPORT ArgIterator {
     public:
-        ArgIterator(QString *str) : m_str(str), m_pos(0), m_prev(-1) {}
+        ArgIterator(QString *str, OsType osType = HostOsInfo::hostOs())
+            : m_str(str), m_pos(0), m_prev(-1), m_osType(osType)
+        {}
         //! Get the next argument. Returns false on encountering end of first command.
         bool next();
         //! True iff the argument is a plain string, possibly after unquoting.
@@ -126,11 +132,14 @@ public:
         QString *m_str, m_value;
         int m_pos, m_prev;
         bool m_simple;
+        OsType m_osType;
     };
 
     class QTCREATOR_UTILS_EXPORT ConstArgIterator {
     public:
-        ConstArgIterator(const QString &str) : m_str(str), m_ait(&m_str) {}
+        ConstArgIterator(const QString &str, OsType osType = HostOsInfo::hostOs())
+            : m_str(str), m_ait(&m_str, osType)
+        {}
         bool next() { return m_ait.next(); }
         bool isSimple() const { return m_ait.isSimple(); }
         QString value() const { return m_ait.value(); }
diff --git a/src/plugins/coreplugin/fileutils.cpp b/src/plugins/coreplugin/fileutils.cpp
index 296beb336d..214ad3a66d 100644
--- a/src/plugins/coreplugin/fileutils.cpp
+++ b/src/plugins/coreplugin/fileutils.cpp
@@ -121,13 +121,14 @@ void FileUtils::openTerminal(const QString &path)
     // Get terminal application
     QString terminalEmulator;
     QStringList args;
-    if (HostOsInfo::isWindowsHost()) {
+    const OsType hostOs = HostOsInfo::hostOs();
+    if (hostOs == OsTypeWindows) {
         terminalEmulator = ConsoleProcess::defaultTerminalEmulator();
-    } else if (HostOsInfo::isMacHost()) {
+    } else if (hostOs == OsTypeMac) {
         terminalEmulator = ICore::resourcePath()
             + QLatin1String("/scripts/openTerminal.command");
     } else {
-        args = QtcProcess::splitArgs(ConsoleProcess::terminalEmulator(ICore::settings()));
+        args = QtcProcess::splitArgs(ConsoleProcess::terminalEmulator(ICore::settings()), hostOs);
         terminalEmulator = args.takeFirst();
         args.append(QString::fromLocal8Bit(qgetenv("SHELL")));
     }
diff --git a/src/plugins/debugger/gdb/gdbengine.cpp b/src/plugins/debugger/gdb/gdbengine.cpp
index f8ef1af82e..b828ceaac0 100644
--- a/src/plugins/debugger/gdb/gdbengine.cpp
+++ b/src/plugins/debugger/gdb/gdbengine.cpp
@@ -4771,19 +4771,20 @@ void GdbEngine::write(const QByteArray &data)
 
 bool GdbEngine::prepareCommand()
 {
-#ifdef Q_OS_WIN
-    Utils::QtcProcess::SplitError perr;
-    startParameters().processArgs = Utils::QtcProcess::prepareArgs(
-                startParameters().processArgs, &perr,
-                &startParameters().environment, &startParameters().workingDirectory);
-    if (perr != Utils::QtcProcess::SplitOk) {
-        // perr == BadQuoting is never returned on Windows
-        // FIXME? QTCREATORBUG-2809
-        handleAdapterStartFailed(QCoreApplication::translate("DebuggerEngine", // Same message in CdbEngine
-                                                             "Debugging complex command lines is currently not supported on Windows."), Core::Id());
-        return false;
+    if (HostOsInfo::isWindowsHost()) {
+        DebuggerStartParameters &sp = startParameters();
+        QtcProcess::SplitError perr;
+        sp.processArgs = QtcProcess::prepareArgs(sp.processArgs, &perr,
+                                                 Utils::HostOsInfo::hostOs(),
+                    &sp.environment, &sp.workingDirectory).toWindowsArgs();
+        if (perr != Utils::QtcProcess::SplitOk) {
+            // perr == BadQuoting is never returned on Windows
+            // FIXME? QTCREATORBUG-2809
+            handleAdapterStartFailed(QCoreApplication::translate("DebuggerEngine", // Same message in CdbEngine
+                                                                 "Debugging complex command lines is currently not supported on Windows."), Core::Id());
+            return false;
+        }
     }
-#endif
     return true;
 }
 
diff --git a/src/plugins/debugger/lldb/lldbengine.cpp b/src/plugins/debugger/lldb/lldbengine.cpp
index 54ac32b28e..f4a7e1da21 100644
--- a/src/plugins/debugger/lldb/lldbengine.cpp
+++ b/src/plugins/debugger/lldb/lldbengine.cpp
@@ -201,11 +201,7 @@ void LldbEngine::setupInferior()
     const DebuggerStartParameters &sp = startParameters();
 
     QString executable;
-#ifdef Q_OS_WIN
-    QString args;
-#else
-    QStringList args;
-#endif
+    Utils::QtcProcess::Arguments args;
     Utils::QtcProcess::prepareCommand(QFileInfo(sp.executable).absoluteFilePath(),
                                       sp.processArgs, &executable, &args);
 
@@ -213,12 +209,8 @@ void LldbEngine::setupInferior()
     cmd.arg("executable", executable);
     cmd.arg("startMode", sp.startMode); // directly relying on this is brittle wrt. insertions, so check it here
     cmd.beginList("processArgs");
-#ifdef Q_OS_WIN
-    // fixme?
-#else
-    foreach (const QString &arg, args)
+    foreach (const QString &arg, args.toUnixArgs())
         cmd.arg(arg.toUtf8().toHex());
-#endif
     cmd.endList();
 
     // it is better not to check the start mode on the python sid (as we would have to duplicate the
diff --git a/src/plugins/git/gitclient.cpp b/src/plugins/git/gitclient.cpp
index 953033ca35..fbe39458f6 100644
--- a/src/plugins/git/gitclient.cpp
+++ b/src/plugins/git/gitclient.cpp
@@ -2990,7 +2990,7 @@ bool GitClient::tryLauchingGitK(const QProcessEnvironment &env,
     VcsBase::VcsBaseOutputWindow *outwin = VcsBase::VcsBaseOutputWindow::instance();
     const QString gitkOpts = settings()->stringValue(GitSettings::gitkOptionsKey);
     if (!gitkOpts.isEmpty())
-        arguments.append(Utils::QtcProcess::splitArgs(gitkOpts));
+        arguments.append(Utils::QtcProcess::splitArgs(gitkOpts, Utils::HostOsInfo::hostOs()));
     if (!fileName.isEmpty())
         arguments << QLatin1String("--") << fileName;
     outwin->appendCommand(workingDirectory, binary, arguments);
diff --git a/src/plugins/ios/iosrunconfiguration.cpp b/src/plugins/ios/iosrunconfiguration.cpp
index b097c490df..4042c4b478 100644
--- a/src/plugins/ios/iosrunconfiguration.cpp
+++ b/src/plugins/ios/iosrunconfiguration.cpp
@@ -51,6 +51,7 @@
 
 using namespace ProjectExplorer;
 using namespace QmakeProjectManager;
+using namespace Utils;
 
 namespace Ios {
 namespace Internal {
@@ -341,27 +342,27 @@ QString IosRunConfigurationWidget::argListToString(const QStringList &args) cons
 
 QStringList IosRunConfigurationWidget::stringToArgList(const QString &args) const
 {
-    Utils::QtcProcess::SplitError err;
-    QStringList res = Utils::QtcProcess::splitArgs(args, false, &err);
+    QtcProcess::SplitError err;
+    QStringList res = QtcProcess::splitArgs(args, OsTypeMac, false, &err);
     switch (err) {
-    case Utils::QtcProcess::SplitOk:
+    case QtcProcess::SplitOk:
         break;
-    case Utils::QtcProcess::BadQuoting:
+    case QtcProcess::BadQuoting:
         if (args.at(args.size()-1) == QLatin1Char('\\')) {
-            res = Utils::QtcProcess::splitArgs(args + QLatin1Char('\\'), false, &err);
-            if (err != Utils::QtcProcess::SplitOk)
-                res = Utils::QtcProcess::splitArgs(args + QLatin1Char('\\') + QLatin1Char('\''),
-                                                   false, &err);
-            if (err != Utils::QtcProcess::SplitOk)
-                res = Utils::QtcProcess::splitArgs(args + QLatin1Char('\\') + QLatin1Char('\"'),
-                                                   false, &err);
+            res = QtcProcess::splitArgs(args + QLatin1Char('\\'), OsTypeMac, false, &err);
+            if (err != QtcProcess::SplitOk)
+                res = QtcProcess::splitArgs(args + QLatin1Char('\\') + QLatin1Char('\''),
+                                            OsTypeMac, false, &err);
+            if (err != QtcProcess::SplitOk)
+                res = QtcProcess::splitArgs(args + QLatin1Char('\\') + QLatin1Char('\"'),
+                                            OsTypeMac, false, &err);
         }
-        if (err != Utils::QtcProcess::SplitOk)
-            res = Utils::QtcProcess::splitArgs(args + QLatin1Char('\''), false, &err);
-        if (err != Utils::QtcProcess::SplitOk)
-            res = Utils::QtcProcess::splitArgs(args + QLatin1Char('\"'), false, &err);
+        if (err != QtcProcess::SplitOk)
+            res = QtcProcess::splitArgs(args + QLatin1Char('\''), OsTypeMac, false, &err);
+        if (err != QtcProcess::SplitOk)
+            res = QtcProcess::splitArgs(args + QLatin1Char('\"'), OsTypeMac, false, &err);
         break;
-    case Utils::QtcProcess::FoundMeta:
+    case QtcProcess::FoundMeta:
         qDebug() << "IosRunConfigurationWidget FoundMeta (should not happen)";
         break;
     }
diff --git a/src/plugins/projectexplorer/gcctoolchain.cpp b/src/plugins/projectexplorer/gcctoolchain.cpp
index ca63fc800d..59647310cc 100644
--- a/src/plugins/projectexplorer/gcctoolchain.cpp
+++ b/src/plugins/projectexplorer/gcctoolchain.cpp
@@ -928,13 +928,14 @@ void GccToolChainConfigWidget::makeReadOnlyImpl()
 QStringList GccToolChainConfigWidget::splitString(const QString &s)
 {
     QtcProcess::SplitError splitError;
-    QStringList res = QtcProcess::splitArgs(s, false, &splitError);
+    const OsType osType = HostOsInfo::hostOs();
+    QStringList res = QtcProcess::splitArgs(s, osType, false, &splitError);
     if (splitError != QtcProcess::SplitOk){
-        res = QtcProcess::splitArgs(s + QLatin1Char('\\'), false, &splitError);
+        res = QtcProcess::splitArgs(s + QLatin1Char('\\'), osType, false, &splitError);
         if (splitError != QtcProcess::SplitOk){
-            res = QtcProcess::splitArgs(s + QLatin1Char('"'), false, &splitError);
+            res = QtcProcess::splitArgs(s + QLatin1Char('"'), osType, false, &splitError);
             if (splitError != QtcProcess::SplitOk)
-                res = QtcProcess::splitArgs(s + QLatin1Char('\''), false, &splitError);
+                res = QtcProcess::splitArgs(s + QLatin1Char('\''), osType, false, &splitError);
         }
     }
     return res;
diff --git a/src/plugins/projectexplorer/processparameters.cpp b/src/plugins/projectexplorer/processparameters.cpp
index e702292c5c..711209b872 100644
--- a/src/plugins/projectexplorer/processparameters.cpp
+++ b/src/plugins/projectexplorer/processparameters.cpp
@@ -169,20 +169,12 @@ QString ProcessParameters::prettyArguments() const
 {
     QString margs = effectiveArguments();
     QString workDir = effectiveWorkingDirectory();
-#ifdef Q_OS_WIN
-    QString args;
-#else
-    QStringList args;
-#endif
     Utils::QtcProcess::SplitError err;
-    args = Utils::QtcProcess::prepareArgs(margs, &err, &m_environment, &workDir);
+    Utils::QtcProcess::Arguments args =
+            Utils::QtcProcess::prepareArgs(margs, &err, Utils::HostOsInfo::hostOs(), &m_environment, &workDir);
     if (err != Utils::QtcProcess::SplitOk)
         return margs; // Sorry, too complex - just fall back.
-#ifdef Q_OS_WIN
-    return args;
-#else
-    return Utils::QtcProcess::joinArgs(args);
-#endif
+    return args.toString();
 }
 
 QString ProcessParameters::summary(const QString &displayName) const
diff --git a/src/plugins/remotelinux/linuxdeviceprocess.cpp b/src/plugins/remotelinux/linuxdeviceprocess.cpp
index c6ec627708..cf60d6a2e0 100644
--- a/src/plugins/remotelinux/linuxdeviceprocess.cpp
+++ b/src/plugins/remotelinux/linuxdeviceprocess.cpp
@@ -69,7 +69,7 @@ QString LinuxDeviceProcess::fullCommandLine() const
     fullCommandLine.append(quote(executable()));
     if (!arguments().isEmpty()) {
         fullCommandLine.append(QLatin1Char(' '));
-        fullCommandLine.append(Utils::QtcProcess::joinArgsUnix(arguments()));
+        fullCommandLine.append(Utils::QtcProcess::joinArgs(arguments(), Utils::OsTypeLinux));
     }
     return fullCommandLine;
 }
diff --git a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp
index 2e5d1b7d37..90bfa3123e 100644
--- a/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp
+++ b/src/plugins/remotelinux/remotelinuxrunconfiguration.cpp
@@ -207,7 +207,7 @@ QString RemoteLinuxRunConfiguration::remoteExecutableFilePath() const
 
 void RemoteLinuxRunConfiguration::setArguments(const QString &args)
 {
-    d->arguments = QtcProcess::splitArgs(args); // TODO: Widget should be list-based.
+    d->arguments = QtcProcess::splitArgs(args, OsTypeLinux); // TODO: Widget should be list-based.
 }
 
 QString RemoteLinuxRunConfiguration::workingDirectory() const
diff --git a/tests/auto/qtcprocess/tst_qtcprocess.cpp b/tests/auto/qtcprocess/tst_qtcprocess.cpp
index 797f08bdb2..a68b278d13 100644
--- a/tests/auto/qtcprocess/tst_qtcprocess.cpp
+++ b/tests/auto/qtcprocess/tst_qtcprocess.cpp
@@ -28,6 +28,7 @@
 ****************************************************************************/
 
 #include <utils/qtcprocess.h>
+#include <utils/hostosinfo.h>
 #include <utils/stringutils.h>
 #include <utils/environment.h>
 
@@ -57,6 +58,7 @@ class tst_QtcProcess : public QObject
 
 private slots:
     void initTestCase();
+
     void splitArgs_data();
     void splitArgs();
     void prepareArgs_data();
@@ -67,120 +69,123 @@ private slots:
     void expandMacros();
     void iterations_data();
     void iterations();
-    void iteratorEdits();
+    void iteratorEditsWindows();
+    void iteratorEditsLinux();
 
 private:
-    Environment env;
-    MacroMapExpander mx;
-#ifdef Q_OS_UNIX
+    void iteratorEditsHelper(OsType osType);
+
+    Environment envWindows;
+    Environment envLinux;
+
+    MacroMapExpander mxWin;
+    MacroMapExpander mxUnix;
     QString homeStr;
     QString home;
-#endif
 };
 
 void tst_QtcProcess::initTestCase()
 {
-#ifdef Q_OS_UNIX
     homeStr = QLatin1String("@HOME@");
     home = QDir::homePath();
-#endif
-
-    env.set("empty", "");
-    env.set("word", "hi");
-    env.set("words", "hi ho");
-    env.set("spacedwords", " hi   ho sucker ");
-
-#ifdef Q_OS_WIN
-    mx.insert("a", "hi");
-    mx.insert("aa", "hi ho");
-
-    mx.insert("b", "h\\i");
-    mx.insert("c", "\\hi");
-    mx.insert("d", "hi\\");
-    mx.insert("ba", "h\\i ho");
-    mx.insert("ca", "\\hi ho");
-    mx.insert("da", "hi ho\\");
-
-    mx.insert("e", "h\"i");
-    mx.insert("f", "\"hi");
-    mx.insert("g", "hi\"");
-
-    mx.insert("h", "h\\\"i");
-    mx.insert("i", "\\\"hi");
-    mx.insert("j", "hi\\\"");
-
-    mx.insert("k", "&special;");
-
-    mx.insert("x", "\\");
-    mx.insert("y", "\"");
-#else
-    mx.insert("a", "hi");
-    mx.insert("b", "hi ho");
-    mx.insert("c", "&special;");
-    mx.insert("d", "h\\i");
-    mx.insert("e", "h\"i");
-    mx.insert("f", "h'i");
-#endif
-    mx.insert("z", "");
+
+    QStringList env;
+    env << "empty=" << "word=hi" << "words=hi ho" << "spacedwords= hi   ho sucker ";
+    envWindows = Environment(env, OsTypeWindows);
+    envLinux = Environment(env, OsTypeLinux);
+
+    mxWin.insert("a", "hi");
+    mxWin.insert("aa", "hi ho");
+
+    mxWin.insert("b", "h\\i");
+    mxWin.insert("c", "\\hi");
+    mxWin.insert("d", "hi\\");
+    mxWin.insert("ba", "h\\i ho");
+    mxWin.insert("ca", "\\hi ho");
+    mxWin.insert("da", "hi ho\\");
+
+    mxWin.insert("e", "h\"i");
+    mxWin.insert("f", "\"hi");
+    mxWin.insert("g", "hi\"");
+
+    mxWin.insert("h", "h\\\"i");
+    mxWin.insert("i", "\\\"hi");
+    mxWin.insert("j", "hi\\\"");
+
+    mxWin.insert("k", "&special;");
+
+    mxWin.insert("x", "\\");
+    mxWin.insert("y", "\"");
+    mxWin.insert("z", "");
+
+    mxUnix.insert("a", "hi");
+    mxUnix.insert("b", "hi ho");
+    mxUnix.insert("c", "&special;");
+    mxUnix.insert("d", "h\\i");
+    mxUnix.insert("e", "h\"i");
+    mxUnix.insert("f", "h'i");
+    mxUnix.insert("z", "");
 }
 
+
 Q_DECLARE_METATYPE(QtcProcess::SplitError)
+Q_DECLARE_METATYPE(Utils::OsType)
 
 void tst_QtcProcess::splitArgs_data()
 {
     QTest::addColumn<QString>("in");
     QTest::addColumn<QString>("out");
     QTest::addColumn<QtcProcess::SplitError>("err");
+    QTest::addColumn<Utils::OsType>("os");
 
     static const struct {
         const char * const in;
         const char * const out;
         const QtcProcess::SplitError err;
+        const OsType os;
     } vals[] = {
-#ifdef Q_OS_WIN
-        { "", "", QtcProcess::SplitOk },
-        { " ", "", QtcProcess::SplitOk },
-        { "hi", "hi", QtcProcess::SplitOk },
-        { "hi ho", "hi ho", QtcProcess::SplitOk },
-        { " hi ho ", "hi ho", QtcProcess::SplitOk },
-        { "\"hi ho\" \"hi\" ho  ", "\"hi ho\" hi ho", QtcProcess::SplitOk },
-        { "\\", "\\", QtcProcess::SplitOk },
-        { "\\\"", "\"\"\\^\"\"\"", QtcProcess::SplitOk },
-        { "\"hi\"\"\"ho\"", "\"hi\"\\^\"\"ho\"", QtcProcess::SplitOk },
-        { "\\\\\\\"", "\"\"\\\\\\^\"\"\"", QtcProcess::SplitOk },
-        { " ^^ ", "\"^^\"", QtcProcess::SplitOk },
-        { "hi\"", "", QtcProcess::BadQuoting },
-        { "hi\"dood", "", QtcProcess::BadQuoting },
-        { "%var%", "%var%", QtcProcess::SplitOk },
-#else
-        { "", "", QtcProcess::SplitOk },
-        { " ", "", QtcProcess::SplitOk },
-        { "hi", "hi", QtcProcess::SplitOk },
-        { "hi ho", "hi ho", QtcProcess::SplitOk },
-        { " hi ho ", "hi ho", QtcProcess::SplitOk },
-        { "'hi ho' \"hi\" ho  ", "'hi ho' hi ho", QtcProcess::SplitOk },
-        { " \\ ", "' '", QtcProcess::SplitOk },
-        { " \\\" ", "'\"'", QtcProcess::SplitOk },
-        { " '\"' ", "'\"'", QtcProcess::SplitOk },
-        { " \"\\\"\" ", "'\"'", QtcProcess::SplitOk },
-        { "hi'", "", QtcProcess::BadQuoting },
-        { "hi\"dood", "", QtcProcess::BadQuoting },
-        { "$var", "'$var'", QtcProcess::SplitOk },
-        { "~", "@HOME@", QtcProcess::SplitOk },
-        { "~ foo", "@HOME@ foo", QtcProcess::SplitOk },
-        { "foo ~", "foo @HOME@", QtcProcess::SplitOk },
-        { "~/foo", "@HOME@/foo", QtcProcess::SplitOk },
-        { "~foo", "'~foo'", QtcProcess::SplitOk },
-#endif
+        { "", "", QtcProcess::SplitOk, OsTypeWindows },
+        { " ", "", QtcProcess::SplitOk, OsTypeWindows },
+        { "hi", "hi", QtcProcess::SplitOk, OsTypeWindows },
+        { "hi ho", "hi ho", QtcProcess::SplitOk, OsTypeWindows },
+        { " hi ho ", "hi ho", QtcProcess::SplitOk, OsTypeWindows },
+        { "\"hi ho\" \"hi\" ho  ", "\"hi ho\" hi ho", QtcProcess::SplitOk, OsTypeWindows },
+        { "\\", "\\", QtcProcess::SplitOk, OsTypeWindows },
+        { "\\\"", "\"\"\\^\"\"\"", QtcProcess::SplitOk, OsTypeWindows },
+        { "\"hi\"\"\"ho\"", "\"hi\"\\^\"\"ho\"", QtcProcess::SplitOk, OsTypeWindows },
+        { "\\\\\\\"", "\"\"\\\\\\^\"\"\"", QtcProcess::SplitOk, OsTypeWindows },
+        { " ^^ ", "\"^^\"", QtcProcess::SplitOk, OsTypeWindows },
+        { "hi\"", "", QtcProcess::BadQuoting, OsTypeWindows },
+        { "hi\"dood", "", QtcProcess::BadQuoting, OsTypeWindows },
+        { "%var%", "%var%", QtcProcess::SplitOk, OsTypeWindows },
+
+        { "", "", QtcProcess::SplitOk, OsTypeLinux },
+        { " ", "", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi", "hi", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi ho", "hi ho", QtcProcess::SplitOk, OsTypeLinux },
+        { " hi ho ", "hi ho", QtcProcess::SplitOk, OsTypeLinux },
+        { "'hi ho' \"hi\" ho  ", "'hi ho' hi ho", QtcProcess::SplitOk, OsTypeLinux },
+        { " \\ ", "' '", QtcProcess::SplitOk, OsTypeLinux },
+        { " \\\" ", "'\"'", QtcProcess::SplitOk, OsTypeLinux },
+        { " '\"' ", "'\"'", QtcProcess::SplitOk, OsTypeLinux },
+        { " \"\\\"\" ", "'\"'", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi'", "", QtcProcess::BadQuoting, OsTypeLinux },
+        { "hi\"dood", "", QtcProcess::BadQuoting, OsTypeLinux },
+        { "$var", "'$var'", QtcProcess::SplitOk, OsTypeLinux },
+        { "~", "@HOME@", QtcProcess::SplitOk, OsTypeLinux },
+        { "~ foo", "@HOME@ foo", QtcProcess::SplitOk, OsTypeLinux },
+        { "foo ~", "foo @HOME@", QtcProcess::SplitOk, OsTypeLinux },
+        { "~/foo", "@HOME@/foo", QtcProcess::SplitOk, OsTypeLinux },
+        { "~foo", "'~foo'", QtcProcess::SplitOk, OsTypeLinux }
     };
 
-    for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++)
+    for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {
+        QString out = QString::fromLatin1(vals[i].out);
+        if (vals[i].os == OsTypeLinux)
+            out.replace(homeStr, home);
         QTest::newRow(vals[i].in) << QString::fromLatin1(vals[i].in)
-                                  << QString::fromLatin1(vals[i].out)
-#ifdef Q_OS_UNIX
-                                     .replace(homeStr, home)
-#endif
-                                  << vals[i].err;
+                                  << out << vals[i].err << vals[i].os;
+    }
 }
 
 void tst_QtcProcess::splitArgs()
@@ -188,9 +193,10 @@ void tst_QtcProcess::splitArgs()
     QFETCH(QString, in);
     QFETCH(QString, out);
     QFETCH(QtcProcess::SplitError, err);
+    QFETCH(Utils::OsType, os);
 
     QtcProcess::SplitError outerr;
-    QString outstr = QtcProcess::joinArgs(QtcProcess::splitArgs(in, false, &outerr));
+    QString outstr = QtcProcess::joinArgs(QtcProcess::splitArgs(in, os, false, &outerr), os);
     QCOMPARE(outerr, err);
     if (err == QtcProcess::SplitOk)
         QCOMPARE(outstr, out);
@@ -201,53 +207,53 @@ void tst_QtcProcess::prepareArgs_data()
     QTest::addColumn<QString>("in");
     QTest::addColumn<QString>("out");
     QTest::addColumn<QtcProcess::SplitError>("err");
+    QTest::addColumn<OsType>("os");
 
     static const struct {
         const char * const in;
         const char * const out;
         const QtcProcess::SplitError err;
+        const OsType os;
     } vals[] = {
-#ifdef Q_OS_WIN
-        { "", "", QtcProcess::SplitOk },
-        { " ", " ", QtcProcess::SplitOk },
-        { "hi", "hi", QtcProcess::SplitOk },
-        { "hi ho", "hi ho", QtcProcess::SplitOk },
-        { " hi ho ", " hi ho ", QtcProcess::SplitOk },
-        { "\"hi ho\" \"hi\" ho  ", "\"hi ho\" \"hi\" ho  ", QtcProcess::SplitOk },
-        { "\\", "\\", QtcProcess::SplitOk },
-        { "\\\"", "\\\"", QtcProcess::SplitOk },
-        { "\"hi\"\"ho\"", "\"hi\"\"ho\"", QtcProcess::SplitOk },
-        { "\\\\\\\"", "\\\\\\\"", QtcProcess::SplitOk },
-        { "^^", "^", QtcProcess::SplitOk },
-        { "hi\"", "hi\"", QtcProcess::SplitOk },
-        { "hi\"dood", "hi\"dood", QtcProcess::SplitOk },
-        { "%var%", "", QtcProcess::FoundMeta },
-        { "echo hi > file", "", QtcProcess::FoundMeta },
-#else
-        { "", "", QtcProcess::SplitOk },
-        { " ", "", QtcProcess::SplitOk },
-        { "hi", "hi", QtcProcess::SplitOk },
-        { "hi ho", "hi ho", QtcProcess::SplitOk },
-        { " hi ho ", "hi ho", QtcProcess::SplitOk },
-        { "'hi ho' \"hi\" ho  ", "'hi ho' hi ho", QtcProcess::SplitOk },
-        { " \\ ", "' '", QtcProcess::SplitOk },
-        { "hi'", "", QtcProcess::BadQuoting },
-        { "hi\"dood", "", QtcProcess::BadQuoting },
-        { "$var", "", QtcProcess::FoundMeta },
-        { "~", "@HOME@", QtcProcess::SplitOk },
-        { "~ foo", "@HOME@ foo", QtcProcess::SplitOk },
-        { "~/foo", "@HOME@/foo", QtcProcess::SplitOk },
-        { "~foo", "", QtcProcess::FoundMeta },
-#endif
+        { " ", " ", QtcProcess::SplitOk, OsTypeWindows },
+        { "", "", QtcProcess::SplitOk, OsTypeWindows },
+        { "hi", "hi", QtcProcess::SplitOk, OsTypeWindows },
+        { "hi ho", "hi ho", QtcProcess::SplitOk, OsTypeWindows },
+        { " hi ho ", " hi ho ", QtcProcess::SplitOk, OsTypeWindows },
+        { "\"hi ho\" \"hi\" ho  ", "\"hi ho\" \"hi\" ho  ", QtcProcess::SplitOk, OsTypeWindows },
+        { "\\", "\\", QtcProcess::SplitOk, OsTypeWindows },
+        { "\\\"", "\\\"", QtcProcess::SplitOk, OsTypeWindows },
+        { "\"hi\"\"ho\"", "\"hi\"\"ho\"", QtcProcess::SplitOk, OsTypeWindows },
+        { "\\\\\\\"", "\\\\\\\"", QtcProcess::SplitOk, OsTypeWindows },
+        { "^^", "^", QtcProcess::SplitOk, OsTypeWindows },
+        { "hi\"", "hi\"", QtcProcess::SplitOk, OsTypeWindows },
+        { "hi\"dood", "hi\"dood", QtcProcess::SplitOk, OsTypeWindows },
+        { "%var%", "", QtcProcess::FoundMeta, OsTypeWindows },
+        { "echo hi > file", "", QtcProcess::FoundMeta, OsTypeWindows },
+
+        { "", "", QtcProcess::SplitOk, OsTypeLinux },
+        { " ", "", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi", "hi", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi ho", "hi ho", QtcProcess::SplitOk, OsTypeLinux },
+        { " hi ho ", "hi ho", QtcProcess::SplitOk, OsTypeLinux },
+        { "'hi ho' \"hi\" ho  ", "'hi ho' hi ho", QtcProcess::SplitOk, OsTypeLinux },
+        { " \\ ", "' '", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi'", "", QtcProcess::BadQuoting, OsTypeLinux },
+        { "hi\"dood", "", QtcProcess::BadQuoting, OsTypeLinux },
+        { "$var", "", QtcProcess::FoundMeta, OsTypeLinux },
+        { "~", "@HOME@", QtcProcess::SplitOk, OsTypeLinux },
+        { "~ foo", "@HOME@ foo", QtcProcess::SplitOk, OsTypeLinux },
+        { "~/foo", "@HOME@/foo", QtcProcess::SplitOk, OsTypeLinux },
+        { "~foo", "", QtcProcess::FoundMeta, OsTypeLinux }
     };
 
-    for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++)
+    for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {
+        QString out = QString::fromLatin1(vals[i].out);
+        if (vals[i].os == OsTypeLinux)
+            out.replace(homeStr, home);
         QTest::newRow(vals[i].in) << QString::fromLatin1(vals[i].in)
-                                  << QString::fromLatin1(vals[i].out)
-#ifdef Q_OS_UNIX
-                                     .replace(homeStr, home)
-#endif
-                                  << vals[i].err;
+                                  << out << vals[i].err << vals[i].os;
+    }
 }
 
 void tst_QtcProcess::prepareArgs()
@@ -255,14 +261,12 @@ void tst_QtcProcess::prepareArgs()
     QFETCH(QString, in);
     QFETCH(QString, out);
     QFETCH(QtcProcess::SplitError, err);
+    QFETCH(OsType, os);
 
     QtcProcess::SplitError outerr;
-    QString outstr;
-#ifdef Q_OS_WIN
-    outstr = QtcProcess::prepareArgs(in, &outerr);
-#else
-    outstr = QtcProcess::joinArgs(QtcProcess::prepareArgs(in, &outerr));
-#endif
+    QtcProcess::Arguments args = QtcProcess::prepareArgs(in, &outerr, os);
+    QString outstr = args.toString();
+
     QCOMPARE(outerr, err);
     if (err == QtcProcess::SplitOk)
         QCOMPARE(outstr, out);
@@ -273,70 +277,73 @@ void tst_QtcProcess::prepareArgsEnv_data()
     QTest::addColumn<QString>("in");
     QTest::addColumn<QString>("out");
     QTest::addColumn<QtcProcess::SplitError>("err");
+    QTest::addColumn<OsType>("os");
 
     static const struct {
         const char * const in;
         const char * const out;
         const QtcProcess::SplitError err;
+        const OsType os;
     } vals[] = {
-#ifdef Q_OS_WIN
-        { "", "", QtcProcess::SplitOk },
-        { " ", " ", QtcProcess::SplitOk },
-        { "hi", "hi", QtcProcess::SplitOk },
-        { "hi ho", "hi ho", QtcProcess::SplitOk },
-        { " hi ho ", " hi ho ", QtcProcess::SplitOk },
-        { "\"hi ho\" \"hi\" ho  ", "\"hi ho\" \"hi\" ho  ", QtcProcess::SplitOk },
-        { "\\", "\\", QtcProcess::SplitOk },
-        { "\\\"", "\\\"", QtcProcess::SplitOk },
-        { "\"hi\"\"ho\"", "\"hi\"\"ho\"", QtcProcess::SplitOk },
-        { "\\\\\\\"", "\\\\\\\"", QtcProcess::SplitOk },
-        { "^^", "^", QtcProcess::SplitOk },
-        { "hi\"", "hi\"", QtcProcess::SplitOk },
-        { "hi\"dood", "hi\"dood", QtcProcess::SplitOk },
-        { "%empty%", "%empty%", QtcProcess::SplitOk }, // Yep, no empty variables on Windows.
-        { "%word%", "hi", QtcProcess::SplitOk },
-        { " %word% ", " hi ", QtcProcess::SplitOk },
-        { "%words%", "hi ho", QtcProcess::SplitOk },
-        { "%nonsense%words%", "%nonsensehi ho", QtcProcess::SplitOk },
-        { "fail%nonsense%words%", "fail%nonsensehi ho", QtcProcess::SplitOk },
-        { "%words%words%", "hi howords%", QtcProcess::SplitOk },
-        { "%words%%words%", "hi hohi ho", QtcProcess::SplitOk },
-        { "echo hi > file", "", QtcProcess::FoundMeta },
-#else
-        { "", "", QtcProcess::SplitOk },
-        { " ", "", QtcProcess::SplitOk },
-        { "hi", "hi", QtcProcess::SplitOk },
-        { "hi ho", "hi ho", QtcProcess::SplitOk },
-        { " hi ho ", "hi ho", QtcProcess::SplitOk },
-        { "'hi ho' \"hi\" ho  ", "'hi ho' hi ho", QtcProcess::SplitOk },
-        { " \\ ", "' '", QtcProcess::SplitOk },
-        { "hi'", "", QtcProcess::BadQuoting },
-        { "hi\"dood", "", QtcProcess::BadQuoting },
-        { "$empty", "", QtcProcess::SplitOk },
-        { "$word", "hi", QtcProcess::SplitOk },
-        { " $word ", "hi", QtcProcess::SplitOk },
-        { "${word}", "hi", QtcProcess::SplitOk },
-        { " ${word} ", "hi", QtcProcess::SplitOk },
-        { "$words", "hi ho", QtcProcess::SplitOk },
-        { "$spacedwords", "hi ho sucker", QtcProcess::SplitOk },
-        { "hi${empty}ho", "hiho", QtcProcess::SplitOk },
-        { "hi${words}ho", "hihi hoho", QtcProcess::SplitOk },
-        { "hi${spacedwords}ho", "hi hi ho sucker ho", QtcProcess::SplitOk },
-        { "${", "", QtcProcess::BadQuoting },
-        { "${var", "", QtcProcess::BadQuoting },
-        { "${var ", "", QtcProcess::FoundMeta },
-        { "\"hi${words}ho\"", "'hihi hoho'", QtcProcess::SplitOk },
-        { "\"hi${spacedwords}ho\"", "'hi hi   ho sucker ho'", QtcProcess::SplitOk },
-        { "\"${", "", QtcProcess::BadQuoting },
-        { "\"${var", "", QtcProcess::BadQuoting },
-        { "\"${var ", "", QtcProcess::FoundMeta },
-#endif
+        { " ", " ", QtcProcess::SplitOk, OsTypeWindows },
+        { "", "", QtcProcess::SplitOk, OsTypeWindows },
+        { "hi", "hi", QtcProcess::SplitOk, OsTypeWindows },
+        { "hi ho", "hi ho", QtcProcess::SplitOk, OsTypeWindows },
+        { " hi ho ", " hi ho ", QtcProcess::SplitOk, OsTypeWindows },
+        { "\"hi ho\" \"hi\" ho  ", "\"hi ho\" \"hi\" ho  ", QtcProcess::SplitOk, OsTypeWindows },
+        { "\\", "\\", QtcProcess::SplitOk, OsTypeWindows },
+        { "\\\"", "\\\"", QtcProcess::SplitOk, OsTypeWindows },
+        { "\"hi\"\"ho\"", "\"hi\"\"ho\"", QtcProcess::SplitOk, OsTypeWindows },
+        { "\\\\\\\"", "\\\\\\\"", QtcProcess::SplitOk, OsTypeWindows },
+        { "^^", "^", QtcProcess::SplitOk, OsTypeWindows },
+        { "hi\"", "hi\"", QtcProcess::SplitOk, OsTypeWindows },
+        { "hi\"dood", "hi\"dood", QtcProcess::SplitOk, OsTypeWindows },
+        { "%empty%", "%empty%", QtcProcess::SplitOk, OsTypeWindows }, // Yep, no empty variables on Windows.
+        { "%word%", "hi", QtcProcess::SplitOk, OsTypeWindows },
+        { " %word% ", " hi ", QtcProcess::SplitOk, OsTypeWindows },
+        { "%words%", "hi ho", QtcProcess::SplitOk, OsTypeWindows },
+        { "%nonsense%words%", "%nonsensehi ho", QtcProcess::SplitOk, OsTypeWindows },
+        { "fail%nonsense%words%", "fail%nonsensehi ho", QtcProcess::SplitOk, OsTypeWindows },
+        { "%words%words%", "hi howords%", QtcProcess::SplitOk, OsTypeWindows },
+        { "%words%%words%", "hi hohi ho", QtcProcess::SplitOk, OsTypeWindows },
+        { "echo hi > file", "", QtcProcess::FoundMeta, OsTypeWindows },
+
+        { "", "", QtcProcess::SplitOk, OsTypeLinux },
+        { " ", "", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi", "hi", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi ho", "hi ho", QtcProcess::SplitOk, OsTypeLinux },
+        { " hi ho ", "hi ho", QtcProcess::SplitOk, OsTypeLinux },
+        { "'hi ho' \"hi\" ho  ", "'hi ho' hi ho", QtcProcess::SplitOk, OsTypeLinux },
+        { " \\ ", "' '", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi'", "", QtcProcess::BadQuoting, OsTypeLinux },
+        { "hi\"dood", "", QtcProcess::BadQuoting, OsTypeLinux },
+        { "$empty", "", QtcProcess::SplitOk, OsTypeLinux },
+        { "$word", "hi", QtcProcess::SplitOk, OsTypeLinux },
+        { " $word ", "hi", QtcProcess::SplitOk, OsTypeLinux },
+        { "${word}", "hi", QtcProcess::SplitOk, OsTypeLinux },
+        { " ${word} ", "hi", QtcProcess::SplitOk, OsTypeLinux },
+        { "$words", "hi ho", QtcProcess::SplitOk, OsTypeLinux },
+        { "$spacedwords", "hi ho sucker", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi${empty}ho", "hiho", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi${words}ho", "hihi hoho", QtcProcess::SplitOk, OsTypeLinux },
+        { "hi${spacedwords}ho", "hi hi ho sucker ho", QtcProcess::SplitOk, OsTypeLinux },
+        { "${", "", QtcProcess::BadQuoting, OsTypeLinux },
+        { "${var", "", QtcProcess::BadQuoting, OsTypeLinux },
+        { "${var ", "", QtcProcess::FoundMeta, OsTypeLinux },
+        { "\"hi${words}ho\"", "'hihi hoho'", QtcProcess::SplitOk, OsTypeLinux },
+        { "\"hi${spacedwords}ho\"", "'hi hi   ho sucker ho'", QtcProcess::SplitOk, OsTypeLinux },
+        { "\"${", "", QtcProcess::BadQuoting, OsTypeLinux },
+        { "\"${var", "", QtcProcess::BadQuoting, OsTypeLinux },
+        { "\"${var ", "", QtcProcess::FoundMeta, OsTypeLinux },
     };
 
-    for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++)
+    for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {
+        QString out = QString::fromLatin1(vals[i].out);
+        if (vals[i].os == OsTypeLinux)
+            out.replace(homeStr, home);
         QTest::newRow(vals[i].in) << QString::fromLatin1(vals[i].in)
-                                  << QString::fromLatin1(vals[i].out)
-                                  << vals[i].err;
+                                  << out << vals[i].err << vals[i].os;
+    }
 }
 
 void tst_QtcProcess::prepareArgsEnv()
@@ -344,14 +351,12 @@ void tst_QtcProcess::prepareArgsEnv()
     QFETCH(QString, in);
     QFETCH(QString, out);
     QFETCH(QtcProcess::SplitError, err);
+    QFETCH(OsType, os);
 
     QtcProcess::SplitError outerr;
-    QString outstr;
-#ifdef Q_OS_WIN
-    outstr = QtcProcess::prepareArgs(in, &outerr, &env);
-#else
-    outstr = QtcProcess::joinArgs(QtcProcess::prepareArgs(in, &outerr, &env));
-#endif
+    QtcProcess::Arguments args = QtcProcess::prepareArgs(in, &outerr, os, os == OsTypeLinux ? &envLinux : &envWindows);
+    QString outstr = args.toString();
+
     QCOMPARE(outerr, err);
     if (err == QtcProcess::SplitOk)
         QCOMPARE(outstr, out);
@@ -362,215 +367,215 @@ void tst_QtcProcess::expandMacros_data()
 {
     QTest::addColumn<QString>("in");
     QTest::addColumn<QString>("out");
+    QTest::addColumn<OsType>("os");
     QChar sp(QLatin1Char(' '));
 
     static const struct {
         const char * const in;
         const char * const out;
+        OsType os;
     } vals[] = {
-#ifdef Q_OS_WIN
-        { "plain", 0 },
-        { "%{a}", "hi" },
-        { "%{aa}", "\"hi ho\"" },
-        { "%{b}", "h\\i" },
-        { "%{c}", "\\hi" },
-        { "%{d}", "hi\\" },
-        { "%{ba}", "\"h\\i ho\"" },
-        { "%{ca}", "\"\\hi ho\"" },
-        { "%{da}", "\"hi ho\\\\\"" }, // or "\"hi ho\"\\"
-        { "%{e}", "\"h\"\\^\"\"i\"" },
-        { "%{f}", "\"\"\\^\"\"hi\"" },
-        { "%{g}", "\"hi\"\\^\"\"\"" },
-        { "%{h}", "\"h\\\\\"\\^\"\"i\"" },
-        { "%{i}", "\"\\\\\"\\^\"\"hi\"" },
-        { "%{j}", "\"hi\\\\\"\\^\"\"\"" },
-        { "%{k}", "\"&special;\"" },
-        { "%{x}", "\\" },
-        { "%{y}", "\"\"\\^\"\"\"" },
-        { "%{z}", "\"\"" },
-        { "^%{z}%{z}", "^%{z}%{z}" }, // stupid user check
-
-        { "quoted", 0 },
-        { "\"%{a}\"", "\"hi\"" },
-        { "\"%{aa}\"", "\"hi ho\"" },
-        { "\"%{b}\"", "\"h\\i\"" },
-        { "\"%{c}\"", "\"\\hi\"" },
-        { "\"%{d}\"", "\"hi\\\\\"" },
-        { "\"%{ba}\"", "\"h\\i ho\"" },
-        { "\"%{ca}\"", "\"\\hi ho\"" },
-        { "\"%{da}\"", "\"hi ho\\\\\"" },
-        { "\"%{e}\"", "\"h\"\\^\"\"i\"" },
-        { "\"%{f}\"", "\"\"\\^\"\"hi\"" },
-        { "\"%{g}\"", "\"hi\"\\^\"\"\"" },
-        { "\"%{h}\"", "\"h\\\\\"\\^\"\"i\"" },
-        { "\"%{i}\"", "\"\\\\\"\\^\"\"hi\"" },
-        { "\"%{j}\"", "\"hi\\\\\"\\^\"\"\"" },
-        { "\"%{k}\"", "\"&special;\"" },
-        { "\"%{x}\"", "\"\\\\\"" },
-        { "\"%{y}\"", "\"\"\\^\"\"\"" },
-        { "\"%{z}\"", "\"\"" },
-
-        { "leading bs", 0 },
-        { "\\%{a}", "\\hi" },
-        { "\\%{aa}", "\\\\\"hi ho\"" },
-        { "\\%{b}", "\\h\\i" },
-        { "\\%{c}", "\\\\hi" },
-        { "\\%{d}", "\\hi\\" },
-        { "\\%{ba}", "\\\\\"h\\i ho\"" },
-        { "\\%{ca}", "\\\\\"\\hi ho\"" },
-        { "\\%{da}", "\\\\\"hi ho\\\\\"" },
-        { "\\%{e}", "\\\\\"h\"\\^\"\"i\"" },
-        { "\\%{f}", "\\\\\"\"\\^\"\"hi\"" },
-        { "\\%{g}", "\\\\\"hi\"\\^\"\"\"" },
-        { "\\%{h}", "\\\\\"h\\\\\"\\^\"\"i\"" },
-        { "\\%{i}", "\\\\\"\\\\\"\\^\"\"hi\"" },
-        { "\\%{j}", "\\\\\"hi\\\\\"\\^\"\"\"" },
-        { "\\%{x}", "\\\\" },
-        { "\\%{y}", "\\\\\"\"\\^\"\"\"" },
-        { "\\%{z}", "\\" },
-
-        { "trailing bs", 0 },
-        { "%{a}\\", "hi\\" },
-        { "%{aa}\\", "\"hi ho\"\\" },
-        { "%{b}\\", "h\\i\\" },
-        { "%{c}\\", "\\hi\\" },
-        { "%{d}\\", "hi\\\\" },
-        { "%{ba}\\", "\"h\\i ho\"\\" },
-        { "%{ca}\\", "\"\\hi ho\"\\" },
-        { "%{da}\\", "\"hi ho\\\\\"\\" },
-        { "%{e}\\", "\"h\"\\^\"\"i\"\\" },
-        { "%{f}\\", "\"\"\\^\"\"hi\"\\" },
-        { "%{g}\\", "\"hi\"\\^\"\"\"\\" },
-        { "%{h}\\", "\"h\\\\\"\\^\"\"i\"\\" },
-        { "%{i}\\", "\"\\\\\"\\^\"\"hi\"\\" },
-        { "%{j}\\", "\"hi\\\\\"\\^\"\"\"\\" },
-        { "%{x}\\", "\\\\" },
-        { "%{y}\\", "\"\"\\^\"\"\"\\" },
-        { "%{z}\\", "\\" },
-
-        { "bs-enclosed", 0 },
-        { "\\%{a}\\", "\\hi\\" },
-        { "\\%{aa}\\", "\\\\\"hi ho\"\\" },
-        { "\\%{b}\\", "\\h\\i\\" },
-        { "\\%{c}\\", "\\\\hi\\" },
-        { "\\%{d}\\", "\\hi\\\\" },
-        { "\\%{ba}\\", "\\\\\"h\\i ho\"\\" },
-        { "\\%{ca}\\", "\\\\\"\\hi ho\"\\" },
-        { "\\%{da}\\", "\\\\\"hi ho\\\\\"\\" },
-        { "\\%{e}\\", "\\\\\"h\"\\^\"\"i\"\\" },
-        { "\\%{f}\\", "\\\\\"\"\\^\"\"hi\"\\" },
-        { "\\%{g}\\", "\\\\\"hi\"\\^\"\"\"\\" },
-        { "\\%{h}\\", "\\\\\"h\\\\\"\\^\"\"i\"\\" },
-        { "\\%{i}\\", "\\\\\"\\\\\"\\^\"\"hi\"\\" },
-        { "\\%{j}\\", "\\\\\"hi\\\\\"\\^\"\"\"\\" },
-        { "\\%{x}\\", "\\\\\\" },
-        { "\\%{y}\\", "\\\\\"\"\\^\"\"\"\\" },
-        { "\\%{z}\\", "\\\\" },
-
-        { "bs-enclosed and trailing literal quote", 0 },
-        { "\\%{a}\\\\\\^\"", "\\hi\\\\\\^\"" },
-        { "\\%{aa}\\\\\\^\"", "\\\\\"hi ho\"\\\\\\^\"" },
-        { "\\%{b}\\\\\\^\"", "\\h\\i\\\\\\^\"" },
-        { "\\%{c}\\\\\\^\"", "\\\\hi\\\\\\^\"" },
-        { "\\%{d}\\\\\\^\"", "\\hi\\\\\\\\\\^\"" },
-        { "\\%{ba}\\\\\\^\"", "\\\\\"h\\i ho\"\\\\\\^\"" },
-        { "\\%{ca}\\\\\\^\"", "\\\\\"\\hi ho\"\\\\\\^\"" },
-        { "\\%{da}\\\\\\^\"", "\\\\\"hi ho\\\\\"\\\\\\^\"" },
-        { "\\%{e}\\\\\\^\"", "\\\\\"h\"\\^\"\"i\"\\\\\\^\"" },
-        { "\\%{f}\\\\\\^\"", "\\\\\"\"\\^\"\"hi\"\\\\\\^\"" },
-        { "\\%{g}\\\\\\^\"", "\\\\\"hi\"\\^\"\"\"\\\\\\^\"" },
-        { "\\%{h}\\\\\\^\"", "\\\\\"h\\\\\"\\^\"\"i\"\\\\\\^\"" },
-        { "\\%{i}\\\\\\^\"", "\\\\\"\\\\\"\\^\"\"hi\"\\\\\\^\"" },
-        { "\\%{j}\\\\\\^\"", "\\\\\"hi\\\\\"\\^\"\"\"\\\\\\^\"" },
-        { "\\%{x}\\\\\\^\"", "\\\\\\\\\\\\\\^\"" },
-        { "\\%{y}\\\\\\^\"", "\\\\\"\"\\^\"\"\"\\\\\\^\"" },
-        { "\\%{z}\\\\\\^\"", "\\\\\\\\\\^\"" },
-
-        { "bs-enclosed and trailing unclosed quote", 0 },
-        { "\\%{a}\\\\\"", "\\hi\\\\\"" },
-        { "\\%{aa}\\\\\"", "\\\\\"hi ho\"\\\\\"" },
-        { "\\%{b}\\\\\"", "\\h\\i\\\\\"" },
-        { "\\%{c}\\\\\"", "\\\\hi\\\\\"" },
-        { "\\%{d}\\\\\"", "\\hi\\\\\\\\\"" },
-        { "\\%{ba}\\\\\"", "\\\\\"h\\i ho\"\\\\\"" },
-        { "\\%{ca}\\\\\"", "\\\\\"\\hi ho\"\\\\\"" },
-        { "\\%{da}\\\\\"", "\\\\\"hi ho\\\\\"\\\\\"" },
-        { "\\%{e}\\\\\"", "\\\\\"h\"\\^\"\"i\"\\\\\"" },
-        { "\\%{f}\\\\\"", "\\\\\"\"\\^\"\"hi\"\\\\\"" },
-        { "\\%{g}\\\\\"", "\\\\\"hi\"\\^\"\"\"\\\\\"" },
-        { "\\%{h}\\\\\"", "\\\\\"h\\\\\"\\^\"\"i\"\\\\\"" },
-        { "\\%{i}\\\\\"", "\\\\\"\\\\\"\\^\"\"hi\"\\\\\"" },
-        { "\\%{j}\\\\\"", "\\\\\"hi\\\\\"\\^\"\"\"\\\\\"" },
-        { "\\%{x}\\\\\"", "\\\\\\\\\\\\\"" },
-        { "\\%{y}\\\\\"", "\\\\\"\"\\^\"\"\"\\\\\"" },
-        { "\\%{z}\\\\\"", "\\\\\\\\\"" },
-
-        { "multi-var", 0 },
-        { "%{x}%{y}%{z}", "\\\\\"\"\\^\"\"\"" },
-        { "%{x}%{z}%{y}%{z}", "\\\\\"\"\\^\"\"\"" },
-        { "%{x}%{z}%{y}", "\\\\\"\"\\^\"\"\"" },
-        { "%{x}\\^\"%{z}", "\\\\\\^\"" },
-        { "%{x}%{z}\\^\"%{z}", "\\\\\\^\"" },
-        { "%{x}%{z}\\^\"", "\\\\\\^\"" },
-        { "%{x}\\%{z}", "\\\\" },
-        { "%{x}%{z}\\%{z}", "\\\\" },
-        { "%{x}%{z}\\", "\\\\" },
-        { "%{aa}%{a}", "\"hi hohi\"" },
-        { "%{aa}%{aa}", "\"hi hohi ho\"" },
-        { "%{aa}:%{aa}", "\"hi ho\":\"hi ho\"" },
-        { "hallo ^|%{aa}^|", "hallo ^|\"hi ho\"^|" },
-
-        { "quoted multi-var", 0 },
-        { "\"%{x}%{y}%{z}\"", "\"\\\\\"\\^\"\"\"" },
-        { "\"%{x}%{z}%{y}%{z}\"", "\"\\\\\"\\^\"\"\"" },
-        { "\"%{x}%{z}%{y}\"", "\"\\\\\"\\^\"\"\"" },
-        { "\"%{x}\"^\"\"%{z}\"", "\"\\\\\"^\"\"\"" },
-        { "\"%{x}%{z}\"^\"\"%{z}\"", "\"\\\\\"^\"\"\"" },
-        { "\"%{x}%{z}\"^\"\"\"", "\"\\\\\"^\"\"\"" },
-        { "\"%{x}\\%{z}\"", "\"\\\\\\\\\"" },
-        { "\"%{x}%{z}\\%{z}\"", "\"\\\\\\\\\"" },
-        { "\"%{x}%{z}\\\\\"", "\"\\\\\\\\\"" },
-        { "\"%{aa}%{a}\"", "\"hi hohi\"" },
-        { "\"%{aa}%{aa}\"", "\"hi hohi ho\"" },
-        { "\"%{aa}:%{aa}\"", "\"hi ho:hi ho\"" },
-#else
-        { "plain", 0 },
-        { "%{a}", "hi" },
-        { "%{b}", "'hi ho'" },
-        { "%{c}", "'&special;'" },
-        { "%{d}", "'h\\i'" },
-        { "%{e}", "'h\"i'" },
-        { "%{f}", "'h'\\''i'" },
-        { "%{z}", "''" },
-        { "\\%{z}%{z}", "\\%{z}%{z}" }, // stupid user check
-
-        { "single-quoted", 0 },
-        { "'%{a}'", "'hi'" },
-        { "'%{b}'", "'hi ho'" },
-        { "'%{c}'", "'&special;'" },
-        { "'%{d}'", "'h\\i'" },
-        { "'%{e}'", "'h\"i'" },
-        { "'%{f}'", "'h'\\''i'" },
-        { "'%{z}'", "''" },
-
-        { "double-quoted", 0 },
-        { "\"%{a}\"", "\"hi\"" },
-        { "\"%{b}\"", "\"hi ho\"" },
-        { "\"%{c}\"", "\"&special;\"" },
-        { "\"%{d}\"", "\"h\\\\i\"" },
-        { "\"%{e}\"", "\"h\\\"i\"" },
-        { "\"%{f}\"", "\"h'i\"" },
-        { "\"%{z}\"", "\"\"" },
-
-        { "complex", 0 },
-        { "echo \"$(echo %{a})\"", "echo \"$(echo hi)\"" },
-        { "echo \"$(echo %{b})\"", "echo \"$(echo 'hi ho')\"" },
-        { "echo \"$(echo \"%{a}\")\"", "echo \"$(echo \"hi\")\"" },
+        { "plain", 0, OsTypeWindows },
+        { "%{a}", "hi", OsTypeWindows },
+        { "%{aa}", "\"hi ho\"", OsTypeWindows },
+        { "%{b}", "h\\i", OsTypeWindows },
+        { "%{c}", "\\hi", OsTypeWindows },
+        { "%{d}", "hi\\", OsTypeWindows },
+        { "%{ba}", "\"h\\i ho\"", OsTypeWindows },
+        { "%{ca}", "\"\\hi ho\"", OsTypeWindows },
+        { "%{da}", "\"hi ho\\\\\"", OsTypeWindows }, // or "\"hi ho\"\\"
+        { "%{e}", "\"h\"\\^\"\"i\"", OsTypeWindows },
+        { "%{f}", "\"\"\\^\"\"hi\"", OsTypeWindows },
+        { "%{g}", "\"hi\"\\^\"\"\"", OsTypeWindows },
+        { "%{h}", "\"h\\\\\"\\^\"\"i\"", OsTypeWindows },
+        { "%{i}", "\"\\\\\"\\^\"\"hi\"", OsTypeWindows },
+        { "%{j}", "\"hi\\\\\"\\^\"\"\"", OsTypeWindows },
+        { "%{k}", "\"&special;\"", OsTypeWindows },
+        { "%{x}", "\\", OsTypeWindows },
+        { "%{y}", "\"\"\\^\"\"\"", OsTypeWindows },
+        { "%{z}", "\"\"", OsTypeWindows },
+        { "^%{z}%{z}", "^%{z}%{z}", OsTypeWindows }, // stupid user check
+
+        { "quoted", 0, OsTypeWindows },
+        { "\"%{a}\"", "\"hi\"", OsTypeWindows },
+        { "\"%{aa}\"", "\"hi ho\"", OsTypeWindows },
+        { "\"%{b}\"", "\"h\\i\"", OsTypeWindows },
+        { "\"%{c}\"", "\"\\hi\"", OsTypeWindows },
+        { "\"%{d}\"", "\"hi\\\\\"", OsTypeWindows },
+        { "\"%{ba}\"", "\"h\\i ho\"", OsTypeWindows },
+        { "\"%{ca}\"", "\"\\hi ho\"", OsTypeWindows },
+        { "\"%{da}\"", "\"hi ho\\\\\"", OsTypeWindows },
+        { "\"%{e}\"", "\"h\"\\^\"\"i\"", OsTypeWindows },
+        { "\"%{f}\"", "\"\"\\^\"\"hi\"", OsTypeWindows },
+        { "\"%{g}\"", "\"hi\"\\^\"\"\"", OsTypeWindows },
+        { "\"%{h}\"", "\"h\\\\\"\\^\"\"i\"", OsTypeWindows },
+        { "\"%{i}\"", "\"\\\\\"\\^\"\"hi\"", OsTypeWindows },
+        { "\"%{j}\"", "\"hi\\\\\"\\^\"\"\"", OsTypeWindows },
+        { "\"%{k}\"", "\"&special;\"", OsTypeWindows },
+        { "\"%{x}\"", "\"\\\\\"", OsTypeWindows },
+        { "\"%{y}\"", "\"\"\\^\"\"\"", OsTypeWindows },
+        { "\"%{z}\"", "\"\"", OsTypeWindows },
+
+        { "leading bs", 0, OsTypeWindows },
+        { "\\%{a}", "\\hi", OsTypeWindows },
+        { "\\%{aa}", "\\\\\"hi ho\"", OsTypeWindows },
+        { "\\%{b}", "\\h\\i", OsTypeWindows },
+        { "\\%{c}", "\\\\hi", OsTypeWindows },
+        { "\\%{d}", "\\hi\\", OsTypeWindows },
+        { "\\%{ba}", "\\\\\"h\\i ho\"", OsTypeWindows },
+        { "\\%{ca}", "\\\\\"\\hi ho\"", OsTypeWindows },
+        { "\\%{da}", "\\\\\"hi ho\\\\\"", OsTypeWindows },
+        { "\\%{e}", "\\\\\"h\"\\^\"\"i\"", OsTypeWindows },
+        { "\\%{f}", "\\\\\"\"\\^\"\"hi\"", OsTypeWindows },
+        { "\\%{g}", "\\\\\"hi\"\\^\"\"\"", OsTypeWindows },
+        { "\\%{h}", "\\\\\"h\\\\\"\\^\"\"i\"", OsTypeWindows },
+        { "\\%{i}", "\\\\\"\\\\\"\\^\"\"hi\"", OsTypeWindows },
+        { "\\%{j}", "\\\\\"hi\\\\\"\\^\"\"\"", OsTypeWindows },
+        { "\\%{x}", "\\\\", OsTypeWindows },
+        { "\\%{y}", "\\\\\"\"\\^\"\"\"", OsTypeWindows },
+        { "\\%{z}", "\\", OsTypeWindows },
+
+        { "trailing bs", 0, OsTypeWindows },
+        { "%{a}\\", "hi\\", OsTypeWindows },
+        { "%{aa}\\", "\"hi ho\"\\", OsTypeWindows },
+        { "%{b}\\", "h\\i\\", OsTypeWindows },
+        { "%{c}\\", "\\hi\\", OsTypeWindows },
+        { "%{d}\\", "hi\\\\", OsTypeWindows },
+        { "%{ba}\\", "\"h\\i ho\"\\", OsTypeWindows },
+        { "%{ca}\\", "\"\\hi ho\"\\", OsTypeWindows },
+        { "%{da}\\", "\"hi ho\\\\\"\\", OsTypeWindows },
+        { "%{e}\\", "\"h\"\\^\"\"i\"\\", OsTypeWindows },
+        { "%{f}\\", "\"\"\\^\"\"hi\"\\", OsTypeWindows },
+        { "%{g}\\", "\"hi\"\\^\"\"\"\\", OsTypeWindows },
+        { "%{h}\\", "\"h\\\\\"\\^\"\"i\"\\", OsTypeWindows },
+        { "%{i}\\", "\"\\\\\"\\^\"\"hi\"\\", OsTypeWindows },
+        { "%{j}\\", "\"hi\\\\\"\\^\"\"\"\\", OsTypeWindows },
+        { "%{x}\\", "\\\\", OsTypeWindows },
+        { "%{y}\\", "\"\"\\^\"\"\"\\", OsTypeWindows },
+        { "%{z}\\", "\\", OsTypeWindows },
+
+        { "bs-enclosed", 0, OsTypeWindows },
+        { "\\%{a}\\", "\\hi\\", OsTypeWindows },
+        { "\\%{aa}\\", "\\\\\"hi ho\"\\", OsTypeWindows },
+        { "\\%{b}\\", "\\h\\i\\", OsTypeWindows },
+        { "\\%{c}\\", "\\\\hi\\", OsTypeWindows },
+        { "\\%{d}\\", "\\hi\\\\", OsTypeWindows },
+        { "\\%{ba}\\", "\\\\\"h\\i ho\"\\", OsTypeWindows },
+        { "\\%{ca}\\", "\\\\\"\\hi ho\"\\", OsTypeWindows },
+        { "\\%{da}\\", "\\\\\"hi ho\\\\\"\\", OsTypeWindows },
+        { "\\%{e}\\", "\\\\\"h\"\\^\"\"i\"\\", OsTypeWindows },
+        { "\\%{f}\\", "\\\\\"\"\\^\"\"hi\"\\", OsTypeWindows },
+        { "\\%{g}\\", "\\\\\"hi\"\\^\"\"\"\\", OsTypeWindows },
+        { "\\%{h}\\", "\\\\\"h\\\\\"\\^\"\"i\"\\", OsTypeWindows },
+        { "\\%{i}\\", "\\\\\"\\\\\"\\^\"\"hi\"\\", OsTypeWindows },
+        { "\\%{j}\\", "\\\\\"hi\\\\\"\\^\"\"\"\\", OsTypeWindows },
+        { "\\%{x}\\", "\\\\\\", OsTypeWindows },
+        { "\\%{y}\\", "\\\\\"\"\\^\"\"\"\\", OsTypeWindows },
+        { "\\%{z}\\", "\\\\", OsTypeWindows },
+
+        { "bs-enclosed and trailing literal quote", 0, OsTypeWindows },
+        { "\\%{a}\\\\\\^\"", "\\hi\\\\\\^\"", OsTypeWindows },
+        { "\\%{aa}\\\\\\^\"", "\\\\\"hi ho\"\\\\\\^\"", OsTypeWindows },
+        { "\\%{b}\\\\\\^\"", "\\h\\i\\\\\\^\"", OsTypeWindows },
+        { "\\%{c}\\\\\\^\"", "\\\\hi\\\\\\^\"", OsTypeWindows },
+        { "\\%{d}\\\\\\^\"", "\\hi\\\\\\\\\\^\"", OsTypeWindows },
+        { "\\%{ba}\\\\\\^\"", "\\\\\"h\\i ho\"\\\\\\^\"", OsTypeWindows },
+        { "\\%{ca}\\\\\\^\"", "\\\\\"\\hi ho\"\\\\\\^\"", OsTypeWindows },
+        { "\\%{da}\\\\\\^\"", "\\\\\"hi ho\\\\\"\\\\\\^\"", OsTypeWindows },
+        { "\\%{e}\\\\\\^\"", "\\\\\"h\"\\^\"\"i\"\\\\\\^\"", OsTypeWindows },
+        { "\\%{f}\\\\\\^\"", "\\\\\"\"\\^\"\"hi\"\\\\\\^\"", OsTypeWindows },
+        { "\\%{g}\\\\\\^\"", "\\\\\"hi\"\\^\"\"\"\\\\\\^\"", OsTypeWindows },
+        { "\\%{h}\\\\\\^\"", "\\\\\"h\\\\\"\\^\"\"i\"\\\\\\^\"", OsTypeWindows },
+        { "\\%{i}\\\\\\^\"", "\\\\\"\\\\\"\\^\"\"hi\"\\\\\\^\"", OsTypeWindows },
+        { "\\%{j}\\\\\\^\"", "\\\\\"hi\\\\\"\\^\"\"\"\\\\\\^\"", OsTypeWindows },
+        { "\\%{x}\\\\\\^\"", "\\\\\\\\\\\\\\^\"", OsTypeWindows },
+        { "\\%{y}\\\\\\^\"", "\\\\\"\"\\^\"\"\"\\\\\\^\"", OsTypeWindows },
+        { "\\%{z}\\\\\\^\"", "\\\\\\\\\\^\"", OsTypeWindows },
+
+        { "bs-enclosed and trailing unclosed quote", 0, OsTypeWindows },
+        { "\\%{a}\\\\\"", "\\hi\\\\\"", OsTypeWindows },
+        { "\\%{aa}\\\\\"", "\\\\\"hi ho\"\\\\\"", OsTypeWindows },
+        { "\\%{b}\\\\\"", "\\h\\i\\\\\"", OsTypeWindows },
+        { "\\%{c}\\\\\"", "\\\\hi\\\\\"", OsTypeWindows },
+        { "\\%{d}\\\\\"", "\\hi\\\\\\\\\"", OsTypeWindows },
+        { "\\%{ba}\\\\\"", "\\\\\"h\\i ho\"\\\\\"", OsTypeWindows },
+        { "\\%{ca}\\\\\"", "\\\\\"\\hi ho\"\\\\\"", OsTypeWindows },
+        { "\\%{da}\\\\\"", "\\\\\"hi ho\\\\\"\\\\\"", OsTypeWindows },
+        { "\\%{e}\\\\\"", "\\\\\"h\"\\^\"\"i\"\\\\\"", OsTypeWindows },
+        { "\\%{f}\\\\\"", "\\\\\"\"\\^\"\"hi\"\\\\\"", OsTypeWindows },
+        { "\\%{g}\\\\\"", "\\\\\"hi\"\\^\"\"\"\\\\\"", OsTypeWindows },
+        { "\\%{h}\\\\\"", "\\\\\"h\\\\\"\\^\"\"i\"\\\\\"", OsTypeWindows },
+        { "\\%{i}\\\\\"", "\\\\\"\\\\\"\\^\"\"hi\"\\\\\"", OsTypeWindows },
+        { "\\%{j}\\\\\"", "\\\\\"hi\\\\\"\\^\"\"\"\\\\\"", OsTypeWindows },
+        { "\\%{x}\\\\\"", "\\\\\\\\\\\\\"", OsTypeWindows },
+        { "\\%{y}\\\\\"", "\\\\\"\"\\^\"\"\"\\\\\"", OsTypeWindows },
+        { "\\%{z}\\\\\"", "\\\\\\\\\"", OsTypeWindows },
+
+        { "multi-var", 0, OsTypeWindows },
+        { "%{x}%{y}%{z}", "\\\\\"\"\\^\"\"\"", OsTypeWindows },
+        { "%{x}%{z}%{y}%{z}", "\\\\\"\"\\^\"\"\"", OsTypeWindows },
+        { "%{x}%{z}%{y}", "\\\\\"\"\\^\"\"\"", OsTypeWindows },
+        { "%{x}\\^\"%{z}", "\\\\\\^\"", OsTypeWindows },
+        { "%{x}%{z}\\^\"%{z}", "\\\\\\^\"", OsTypeWindows },
+        { "%{x}%{z}\\^\"", "\\\\\\^\"", OsTypeWindows },
+        { "%{x}\\%{z}", "\\\\", OsTypeWindows },
+        { "%{x}%{z}\\%{z}", "\\\\", OsTypeWindows },
+        { "%{x}%{z}\\", "\\\\", OsTypeWindows },
+        { "%{aa}%{a}", "\"hi hohi\"", OsTypeWindows },
+        { "%{aa}%{aa}", "\"hi hohi ho\"", OsTypeWindows },
+        { "%{aa}:%{aa}", "\"hi ho\":\"hi ho\"", OsTypeWindows },
+        { "hallo ^|%{aa}^|", "hallo ^|\"hi ho\"^|", OsTypeWindows },
+
+        { "quoted multi-var", 0, OsTypeWindows },
+        { "\"%{x}%{y}%{z}\"", "\"\\\\\"\\^\"\"\"", OsTypeWindows },
+        { "\"%{x}%{z}%{y}%{z}\"", "\"\\\\\"\\^\"\"\"", OsTypeWindows },
+        { "\"%{x}%{z}%{y}\"", "\"\\\\\"\\^\"\"\"", OsTypeWindows },
+        { "\"%{x}\"^\"\"%{z}\"", "\"\\\\\"^\"\"\"", OsTypeWindows },
+        { "\"%{x}%{z}\"^\"\"%{z}\"", "\"\\\\\"^\"\"\"", OsTypeWindows },
+        { "\"%{x}%{z}\"^\"\"\"", "\"\\\\\"^\"\"\"", OsTypeWindows },
+        { "\"%{x}\\%{z}\"", "\"\\\\\\\\\"", OsTypeWindows },
+        { "\"%{x}%{z}\\%{z}\"", "\"\\\\\\\\\"", OsTypeWindows },
+        { "\"%{x}%{z}\\\\\"", "\"\\\\\\\\\"", OsTypeWindows },
+        { "\"%{aa}%{a}\"", "\"hi hohi\"", OsTypeWindows },
+        { "\"%{aa}%{aa}\"", "\"hi hohi ho\"", OsTypeWindows },
+        { "\"%{aa}:%{aa}\"", "\"hi ho:hi ho\"", OsTypeWindows },
+
+        { "plain", 0, OsTypeLinux },
+        { "%{a}", "hi", OsTypeLinux },
+        { "%{b}", "'hi ho'", OsTypeLinux },
+        { "%{c}", "'&special;'", OsTypeLinux },
+        { "%{d}", "'h\\i'", OsTypeLinux },
+        { "%{e}", "'h\"i'", OsTypeLinux },
+        { "%{f}", "'h'\\''i'", OsTypeLinux },
+        { "%{z}", "''", OsTypeLinux },
+        { "\\%{z}%{z}", "\\%{z}%{z}", OsTypeLinux }, // stupid user check
+
+        { "single-quoted", 0, OsTypeLinux },
+        { "'%{a}'", "'hi'", OsTypeLinux },
+        { "'%{b}'", "'hi ho'", OsTypeLinux },
+        { "'%{c}'", "'&special;'", OsTypeLinux },
+        { "'%{d}'", "'h\\i'", OsTypeLinux },
+        { "'%{e}'", "'h\"i'", OsTypeLinux },
+        { "'%{f}'", "'h'\\''i'", OsTypeLinux },
+        { "'%{z}'", "''", OsTypeLinux },
+
+        { "double-quoted", 0, OsTypeLinux },
+        { "\"%{a}\"", "\"hi\"", OsTypeLinux },
+        { "\"%{b}\"", "\"hi ho\"", OsTypeLinux },
+        { "\"%{c}\"", "\"&special;\"", OsTypeLinux },
+        { "\"%{d}\"", "\"h\\\\i\"", OsTypeLinux },
+        { "\"%{e}\"", "\"h\\\"i\"", OsTypeLinux },
+        { "\"%{f}\"", "\"h'i\"", OsTypeLinux },
+        { "\"%{z}\"", "\"\"", OsTypeLinux },
+
+        { "complex", 0, OsTypeLinux },
+        { "echo \"$(echo %{a})\"", "echo \"$(echo hi)\"", OsTypeLinux },
+        { "echo \"$(echo %{b})\"", "echo \"$(echo 'hi ho')\"", OsTypeLinux },
+        { "echo \"$(echo \"%{a}\")\"", "echo \"$(echo \"hi\")\"", OsTypeLinux },
         // These make no sense shell-wise, but they test expando nesting
-        { "echo \"%{echo %{a}}\"", "echo \"%{echo hi}\"" },
-        { "echo \"%{echo %{b}}\"", "echo \"%{echo hi ho}\"" },
-        { "echo \"%{echo \"%{a}\"}\"", "echo \"%{echo \"hi\"}\"" },
-#endif
+        { "echo \"%{echo %{a}}\"", "echo \"%{echo hi}\"", OsTypeLinux },
+        { "echo \"%{echo %{b}}\"", "echo \"%{echo hi ho}\"", OsTypeLinux },
+        { "echo \"%{echo \"%{a}\"}\"", "echo \"%{echo \"hi\"}\"", OsTypeLinux },
     };
 
     const char *title = 0;
@@ -581,10 +586,12 @@ void tst_QtcProcess::expandMacros_data()
             char buf[80];
             sprintf(buf, "%s: %s", title, vals[i].in);
             QTest::newRow(buf) << QString::fromLatin1(vals[i].in)
-                               << QString::fromLatin1(vals[i].out);
+                               << QString::fromLatin1(vals[i].out)
+                               << vals[i].os;
             sprintf(buf, "padded %s: %s", title, vals[i].in);
             QTest::newRow(buf) << (sp + QString::fromLatin1(vals[i].in) + sp)
-                               << (sp + QString::fromLatin1(vals[i].out) + sp);
+                               << (sp + QString::fromLatin1(vals[i].out) + sp)
+                               << vals[i].os;
         }
     }
 }
@@ -593,8 +600,12 @@ void tst_QtcProcess::expandMacros()
 {
     QFETCH(QString, in);
     QFETCH(QString, out);
+    QFETCH(OsType, os);
 
-    QtcProcess::expandMacros(&in, &mx);
+    if (os == OsTypeWindows)
+        QtcProcess::expandMacros(&in, &mxWin, os);
+    else
+        QtcProcess::expandMacros(&in, &mxUnix, os);
     QCOMPARE(in, out);
 }
 
@@ -602,75 +613,78 @@ void tst_QtcProcess::iterations_data()
 {
     QTest::addColumn<QString>("in");
     QTest::addColumn<QString>("out");
+    QTest::addColumn<OsType>("os");
 
     static const struct {
         const char * const in;
         const char * const out;
+        OsType os;
     } vals[] = {
-#ifdef Q_OS_WIN
-        { "", "" },
-        { "hi", "hi" },
-        { "  hi ", "hi" },
-        { "hi ho", "hi ho" },
-        { "\"hi ho\" sucker", "\"hi ho\" sucker" },
-        { "\"hi\"^\"\"ho\" sucker", "\"hi\"\\^\"\"ho\" sucker" },
-        { "\"hi\"\\^\"\"ho\" sucker", "\"hi\"\\^\"\"ho\" sucker" },
-        { "hi^|ho", "\"hi|ho\"" },
-        { "c:\\", "c:\\" },
-        { "\"c:\\\\\"", "c:\\" },
-        { "\\hi\\ho", "\\hi\\ho" },
-        { "hi null%", "hi null%" },
-        { "hi null% ho", "hi null% ho" },
-        { "hi null%here ho", "hi null%here ho" },
-        { "hi null%here%too ho", "hi {} ho" },
-        { "echo hello | more", "echo hello" },
-        { "echo hello| more", "echo hello" },
-#else
-        { "", "" },
-        { " ", "" },
-        { "hi", "hi" },
-        { "  hi ", "hi" },
-        { "'hi'", "hi" },
-        { "hi ho", "hi ho" },
-        { "\"hi ho\" sucker", "'hi ho' sucker" },
-        { "\"hi\\\"ho\" sucker", "'hi\"ho' sucker" },
-        { "\"hi'ho\" sucker", "'hi'\\''ho' sucker" },
-        { "'hi ho' sucker", "'hi ho' sucker" },
-        { "\\\\", "'\\'" },
-        { "'\\'", "'\\'" },
-        { "hi 'null${here}too' ho", "hi 'null${here}too' ho" },
-        { "hi null${here}too ho", "hi {} ho" },
-        { "hi $(echo $dollar cent) ho", "hi {} ho" },
-        { "hi `echo $dollar \\`echo cent\\` | cat` ho", "hi {} ho" },
-        { "echo hello | more", "echo hello" },
-        { "echo hello| more", "echo hello" },
-#endif
+        { "", "", OsTypeWindows },
+        { "hi", "hi", OsTypeWindows },
+        { "  hi ", "hi", OsTypeWindows },
+        { "hi ho", "hi ho", OsTypeWindows },
+        { "\"hi ho\" sucker", "\"hi ho\" sucker", OsTypeWindows },
+        { "\"hi\"^\"\"ho\" sucker", "\"hi\"\\^\"\"ho\" sucker", OsTypeWindows },
+        { "\"hi\"\\^\"\"ho\" sucker", "\"hi\"\\^\"\"ho\" sucker", OsTypeWindows },
+        { "hi^|ho", "\"hi|ho\"", OsTypeWindows },
+        { "c:\\", "c:\\", OsTypeWindows },
+        { "\"c:\\\\\"", "c:\\", OsTypeWindows },
+        { "\\hi\\ho", "\\hi\\ho", OsTypeWindows },
+        { "hi null%", "hi null%", OsTypeWindows },
+        { "hi null% ho", "hi null% ho", OsTypeWindows },
+        { "hi null%here ho", "hi null%here ho", OsTypeWindows },
+        { "hi null%here%too ho", "hi {} ho", OsTypeWindows },
+        { "echo hello | more", "echo hello", OsTypeWindows },
+        { "echo hello| more", "echo hello", OsTypeWindows },
+
+        { "", "", OsTypeLinux },
+        { " ", "", OsTypeLinux },
+        { "hi", "hi", OsTypeLinux },
+        { "  hi ", "hi", OsTypeLinux },
+        { "'hi'", "hi", OsTypeLinux },
+        { "hi ho", "hi ho", OsTypeLinux },
+        { "\"hi ho\" sucker", "'hi ho' sucker", OsTypeLinux },
+        { "\"hi\\\"ho\" sucker", "'hi\"ho' sucker", OsTypeLinux },
+        { "\"hi'ho\" sucker", "'hi'\\''ho' sucker", OsTypeLinux },
+        { "'hi ho' sucker", "'hi ho' sucker", OsTypeLinux },
+        { "\\\\", "'\\'", OsTypeLinux },
+        { "'\\'", "'\\'", OsTypeLinux },
+        { "hi 'null${here}too' ho", "hi 'null${here}too' ho", OsTypeLinux },
+        { "hi null${here}too ho", "hi {} ho", OsTypeLinux },
+        { "hi $(echo $dollar cent) ho", "hi {} ho", OsTypeLinux },
+        { "hi `echo $dollar \\`echo cent\\` | cat` ho", "hi {} ho", OsTypeLinux },
+        { "echo hello | more", "echo hello", OsTypeLinux },
+        { "echo hello| more", "echo hello", OsTypeLinux },
     };
 
     for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++)
         QTest::newRow(vals[i].in) << QString::fromLatin1(vals[i].in)
-                                  << QString::fromLatin1(vals[i].out);
+                                  << QString::fromLatin1(vals[i].out)
+                                  << vals[i].os;
 }
 
 void tst_QtcProcess::iterations()
 {
     QFETCH(QString, in);
     QFETCH(QString, out);
+    QFETCH(OsType, os);
 
     QString outstr;
-    for (QtcProcess::ArgIterator ait(&in); ait.next(); )
+    for (QtcProcess::ArgIterator ait(&in, os); ait.next(); ) {
         if (ait.isSimple())
-            QtcProcess::addArg(&outstr, ait.value());
+            QtcProcess::addArg(&outstr, ait.value(), os);
         else
             QtcProcess::addArgs(&outstr, "{}");
+    }
     QCOMPARE(outstr, out);
 }
 
-void tst_QtcProcess::iteratorEdits()
+void tst_QtcProcess::iteratorEditsHelper(OsType osType)
 {
     QString in1 = "one two three", in2 = in1, in3 = in1, in4 = in1, in5 = in1;
 
-    QtcProcess::ArgIterator ait1(&in1);
+    QtcProcess::ArgIterator ait1(&in1, osType);
     QVERIFY(ait1.next());
     ait1.deleteArg();
     QVERIFY(ait1.next());
@@ -680,7 +694,7 @@ void tst_QtcProcess::iteratorEdits()
     ait1.appendArg("four");
     QCOMPARE(in1, QString::fromLatin1("two three four"));
 
-    QtcProcess::ArgIterator ait2(&in2);
+    QtcProcess::ArgIterator ait2(&in2, osType);
     QVERIFY(ait2.next());
     QVERIFY(ait2.next());
     ait2.deleteArg();
@@ -689,7 +703,7 @@ void tst_QtcProcess::iteratorEdits()
     QVERIFY(!ait2.next());
     QCOMPARE(in2, QString::fromLatin1("one three four"));
 
-    QtcProcess::ArgIterator ait3(&in3);
+    QtcProcess::ArgIterator ait3(&in3, osType);
     QVERIFY(ait3.next());
     ait3.appendArg("one-b");
     QVERIFY(ait3.next());
@@ -698,7 +712,7 @@ void tst_QtcProcess::iteratorEdits()
     QVERIFY(!ait3.next());
     QCOMPARE(in3, QString::fromLatin1("one one-b two"));
 
-    QtcProcess::ArgIterator ait4(&in4);
+    QtcProcess::ArgIterator ait4(&in4, osType);
     ait4.appendArg("pre-one");
     QVERIFY(ait4.next());
     QVERIFY(ait4.next());
@@ -707,7 +721,7 @@ void tst_QtcProcess::iteratorEdits()
     QVERIFY(!ait4.next());
     QCOMPARE(in4, QString::fromLatin1("pre-one one two"));
 
-    QtcProcess::ArgIterator ait5(&in5);
+    QtcProcess::ArgIterator ait5(&in5, osType);
     QVERIFY(ait5.next());
     QVERIFY(ait5.next());
     QVERIFY(ait5.next());
@@ -717,6 +731,16 @@ void tst_QtcProcess::iteratorEdits()
     QCOMPARE(in5, QString::fromLatin1("one two"));
 }
 
+void tst_QtcProcess::iteratorEditsWindows()
+{
+    iteratorEditsHelper(OsTypeWindows);
+}
+
+void tst_QtcProcess::iteratorEditsLinux()
+{
+    iteratorEditsHelper(OsTypeLinux);
+}
+
 QTEST_MAIN(tst_QtcProcess)
 
 #include "tst_qtcprocess.moc"
-- 
GitLab