diff --git a/src/libs/utils/qtcprocess.cpp b/src/libs/utils/qtcprocess.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..dab401d78cff9a6c99973150039b91feca4b64f3
--- /dev/null
+++ b/src/libs/utils/qtcprocess.cpp
@@ -0,0 +1,1387 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include "qtcprocess.h"
+#include "stringutils.h"
+
+#ifdef Q_OS_WIN
+#include <QtCore/QDir>
+#endif
+
+using namespace Utils;
+
+/**
+ * \fn QStringList QtcProcess::splitArgs(
+ *         const QString &args, bool abortOnMeta, SplitError *err, const Environment *env)
+ *
+ * Splits \a args according to system shell word splitting and quoting rules.
+ *
+ * \section Unix
+ *
+ * The behavior is based on the POSIX shell and bash:
+ * \list
+ * \li Whitespace splits tokens
+ * \li The backslash quotes the following character
+ * \li A string enclosed in single quotes is not split. No shell meta
+ *     characters are interpreted.
+ * \li A string enclosed in double quotes is not split. Within the string,
+ *     the backslash quotes shell meta characters - if it is followed
+ *     by a "meaningless" character, the backslash is output verbatim.
+ * \list
+ * If \a abortOnMeta is \c false, only the splitting and quoting rules apply,
+ * while other meta characters (substitutions, redirections, etc.) are ignored.
+ * If \a abortOnMeta is \c true, encounters of unhandled meta characters are
+ * treated as errors.
+ *
+ * \section Windows
+ *
+ * The behavior is defined by the Microsoft C runtime:
+ * \list
+ * \li Whitespace splits tokens
+ * \li A string enclosed in double quotes is not split
+ *   \list
+ *   \li 3N double quotes within a quoted string yield N literal quotes.
+ *     This is not documented on MSDN.
+ *   \endlist
+ * \li Backslashes have special semantics iff they are followed by a double
+ *     quote:
+ *   \list
+ *   \li 2N backslashes + double quote => N backslashes and begin/end quoting
+ *   \li 2N+1 backslashes + double quote => N backslashes + literal quote
+ *   \endlist
+ * \endlist
+ * Qt and many other implementations comply with this standard, but many do not.
+ *
+ * If \a abortOnMeta is \c true, cmd shell semantics are applied before
+ * proceeding with word splitting:
+ * \list
+ * \li Cmd ignores \em all special chars between double quotes.
+ *     Note that the quotes are \em not removed at this stage - the
+ *     tokenization rules described above still apply.
+ * \li The \c circumflex is the escape char for everything including itself.
+ * \endlist
+ * As the quoting levels are independent from each other and have different
+ * semantics, you need a command line like \c{"foo "\^"" bar"} to get
+ * \c{foo " bar}.
+ *
+ * \param cmd the command to split
+ * \param abortOnMeta see above
+ * \param err if not NULL, a status code will be stored at the pointer
+ *  target, see \ref SplitError
+ * \param env if not NULL, perform variable substitution with the
+ *  given environment.
+ * \return a list of unquoted words or an empty list if an error occurred
+ */
+
+#ifdef Q_OS_WIN
+
+inline static bool isMetaChar(ushort c)
+{
+    static const uchar iqm[] = {
+        0x00, 0x00, 0x00, 0x00, 0x40, 0x03, 0x00, 0x50,
+        0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10
+    }; // &()<>|
+
+    return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
+}
+
+static void envExpand(QString &args, const Environment *env, const QString *pwd)
+{
+    static const QString cdName = QLatin1String("CD");
+    int off = 0;
+  next:
+    for (int prev = -1, that;
+         (that = args.indexOf(QLatin1Char('%'), off)) >= 0;
+         prev = that, off = that + 1) {
+        if (prev >= 0) {
+            const QString var = args.mid(prev + 1, that - prev - 1).toUpper();
+            const QString val = (var == cdName && pwd && !pwd->isEmpty())
+                                    ? QDir::toNativeSeparators(*pwd) : env->value(var);
+            if (!val.isEmpty()) { // Empty values are impossible, so this is an existence check
+                args.replace(prev, that - prev + 1, val);
+                off = prev + val.length();
+                goto next;
+            }
+        }
+    }
+}
+
+QString QtcProcess::prepareArgs(const QString &_args, SplitError *err,
+                                const Environment *env, const QString *pwd)
+{
+    QString args(_args);
+
+    if (env) {
+        envExpand(args, env, pwd);
+    } else {
+        if (args.indexOf(QLatin1Char('%')) >= 0) {
+            if (err)
+                *err = FoundMeta;
+            return QString();
+        }
+    }
+
+    if (!args.isEmpty() && args.unicode()[0].unicode() == '@')
+        args.remove(0, 1);
+
+    for (int p = 0; p < args.length(); p++) {
+        ushort c = args.unicode()[p].unicode();
+        if (c == '^') {
+            args.remove(p, 1);
+        } else if (c == '"') {
+            do {
+                if (++p == args.length())
+                    break; // For cmd, this is no error.
+            } while (args.unicode()[p].unicode() != '"');
+        } else if (isMetaChar(c)) {
+            if (err)
+                *err = FoundMeta;
+            return QString();
+        }
+    }
+
+    if (err)
+        *err = SplitOk;
+    return args;
+}
+
+inline static bool isWhiteSpace(ushort c)
+{
+    return c == ' ' || c == '\t';
+}
+
+static QStringList doSplitArgs(const QString &args, QtcProcess::SplitError *err)
+{
+    QStringList ret;
+
+    if (err)
+        *err = QtcProcess::SplitOk;
+
+    int p = 0;
+    const int length = args.length();
+    forever {
+        forever {
+            if (p == length)
+                return ret;
+            if (!isWhiteSpace(args.unicode()[p].unicode()))
+                break;
+            ++p;
+        }
+
+        QString arg;
+        bool inquote = false;
+        forever {
+            bool copy = true; // copy this char
+            int bslashes = 0; // number of preceding backslashes to insert
+            while (p < length && args.unicode()[p] == QLatin1Char('\\')) {
+                ++p;
+                ++bslashes;
+            }
+            if (p < length && args.unicode()[p] == QLatin1Char('"')) {
+                if (!(bslashes & 1)) {
+                    // Even number of backslashes, so the quote is not escaped.
+                    if (inquote) {
+                        if (p + 1 < length && args.unicode()[p + 1] == QLatin1Char('"')) {
+                            // This is not documented on MSDN.
+                            // Two consecutive quotes make a literal quote. Brain damage:
+                            // this still closes the quoting, so a 3rd quote is required,
+                            // which makes the runtime's quoting run out of sync with the
+                            // shell's one unless the 2nd quote is escaped.
+                            ++p;
+                        } else {
+                            // Closing quote
+                            copy = false;
+                        }
+                        inquote = false;
+                    } else {
+                        // Opening quote
+                        copy = false;
+                        inquote = true;
+                    }
+                }
+                bslashes >>= 1;
+            }
+
+            while (--bslashes >= 0)
+                arg.append(QLatin1Char('\\'));
+
+            if (p == length || (!inquote && isWhiteSpace(args.unicode()[p].unicode()))) {
+                ret.append(arg);
+                if (inquote) {
+                    if (err)
+                        *err = QtcProcess::BadQuoting;
+                    return QStringList();
+                }
+                break;
+            }
+
+            if (copy)
+                arg.append(args.unicode()[p]);
+            ++p;
+        }
+    }
+    //not reached
+}
+
+QStringList QtcProcess::splitArgs(const QString &_args, bool abortOnMeta, SplitError *err,
+                                  const Environment *env, const QString *pwd)
+{
+    if (abortOnMeta) {
+        SplitError perr;
+        if (!err)
+            err = &perr;
+        QString args = prepareArgs(_args, &perr, env, pwd);
+        if (*err != SplitOk)
+            return QStringList();
+        return doSplitArgs(args, err);
+    } else {
+        QString args = _args;
+        if (env)
+            envExpand(args, env, pwd);
+        return doSplitArgs(args, err);
+    }
+}
+
+#else // Q_OS_WIN
+
+inline static bool isQuoteMeta(QChar cUnicode)
+{
+    char c = cUnicode.toAscii();
+    return c == '\\' || c == '\'' || c == '"' || c == '$';
+}
+
+inline static bool isMeta(QChar cUnicode)
+{
+    static const uchar iqm[] = {
+        0x00, 0x00, 0x00, 0x00, 0xdc, 0x07, 0x00, 0xd8,
+        0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x38
+    }; // \'"$`<>|;&(){}*?#[]
+
+    uint c = cUnicode.unicode();
+
+    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 const QString pwdName = QLatin1String("PWD");
+    QStringList ret;
+
+    for (int pos = 0; ; ) {
+        QChar c;
+        do {
+            if (pos >= args.length())
+                goto okret;
+            c = args.unicode()[pos++];
+        } while (c.isSpace());
+        QString cret;
+        bool hadWord = false;
+        do {
+            if (c == QLatin1Char('\'')) {
+                int spos = pos;
+                do {
+                    if (pos >= args.length())
+                        goto quoteerr;
+                    c = args.unicode()[pos++];
+                } while (c != QLatin1Char('\''));
+                cret += args.mid(spos, pos-spos-1);
+                hadWord = true;
+            } else if (c == QLatin1Char('"')) {
+                for (;;) {
+                    if (pos >= args.length())
+                        goto quoteerr;
+                    c = args.unicode()[pos++];
+                  nextq:
+                    if (c == QLatin1Char('"'))
+                        break;
+                    if (c == QLatin1Char('\\')) {
+                        if (pos >= args.length())
+                            goto quoteerr;
+                        c = args.unicode()[pos++];
+                        if (c != QLatin1Char('"') &&
+                            c != QLatin1Char('\\') &&
+                            !(abortOnMeta &&
+                              (c == QLatin1Char('$') ||
+                               c == QLatin1Char('`'))))
+                            cret += QLatin1Char('\\');
+                    } else if (c == QLatin1Char('$') && env) {
+                        if (pos >= args.length())
+                            goto quoteerr;
+                        c = args.unicode()[pos++];
+                        bool braced = false;
+                        if (c == QLatin1Char('{')) {
+                            if (pos >= args.length())
+                                goto quoteerr;
+                            c = args.unicode()[pos++];
+                            braced = true;
+                        }
+                        QString var;
+                        while (c.isLetterOrNumber() || c == QLatin1Char('_')) {
+                            var += c;
+                            if (pos >= args.length())
+                                goto quoteerr;
+                            c = args.unicode()[pos++];
+                        }
+                        if (var == pwdName && pwd && !pwd->isEmpty()) {
+                            cret += *pwd;
+                        } else {
+                            Environment::const_iterator vit = env->constFind(var);
+                            if (vit == env->constEnd()) {
+                                if (abortOnMeta)
+                                    goto metaerr; // Assume this is a shell builtin
+                            } else {
+                                cret += *vit;
+                            }
+                        }
+                        if (!braced)
+                            goto nextq;
+                        if (c != QLatin1Char('}')) {
+                            if (abortOnMeta)
+                                goto metaerr; // Assume this is a complex expansion
+                            goto quoteerr; // Otherwise it's just garbage
+                        }
+                        continue;
+                    } else if (abortOnMeta &&
+                               (c == QLatin1Char('$') ||
+                                c == QLatin1Char('`'))) {
+                        goto metaerr;
+                    }
+                    cret += c;
+                }
+                hadWord = true;
+            } else if (c == QLatin1Char('$') && env) {
+                if (pos >= args.length())
+                    goto quoteerr; // Bash just takes it verbatim, but whatever
+                c = args.unicode()[pos++];
+                bool braced = false;
+                if (c == QLatin1Char('{')) {
+                    if (pos >= args.length())
+                        goto quoteerr;
+                    c = args.unicode()[pos++];
+                    braced = true;
+                }
+                QString var;
+                while (c.isLetterOrNumber() || c == QLatin1Char('_')) {
+                    var += c;
+                    if (pos >= args.length()) {
+                        if (braced)
+                            goto quoteerr;
+                        c = QLatin1Char(' ');
+                        break;
+                    }
+                    c = args.unicode()[pos++];
+                }
+                QString val;
+                if (var == pwdName && pwd && !pwd->isEmpty()) {
+                    val = *pwd;
+                } else {
+                    Environment::const_iterator vit = env->constFind(var);
+                    if (vit == env->constEnd()) {
+                        if (abortOnMeta)
+                            goto metaerr; // Assume this is a shell builtin
+                    } else {
+                        val = *vit;
+                    }
+                }
+                for (int i = 0; i < val.length(); i++) {
+                    QChar cc = val.unicode()[i];
+                    if (cc == 9 || cc == 10 || cc == 32) {
+                        if (hadWord) {
+                            ret += cret;
+                            cret.clear();
+                            hadWord = false;
+                        }
+                    } else {
+                        cret += cc;
+                        hadWord = true;
+                    }
+                }
+                if (!braced)
+                    goto nextc;
+                if (c != QLatin1Char('}')) {
+                    if (abortOnMeta)
+                        goto metaerr; // Assume this is a complex expansion
+                    goto quoteerr; // Otherwise it's just garbage
+                }
+            } else {
+                if (c == QLatin1Char('\\')) {
+                    if (pos >= args.length())
+                        goto quoteerr;
+                    c = args.unicode()[pos++];
+                } else if (abortOnMeta && isMeta(c)) {
+                    goto metaerr;
+                }
+                cret += c;
+                hadWord = true;
+            }
+            if (pos >= args.length())
+                break;
+            c = args.unicode()[pos++];
+          nextc: ;
+        } while (!c.isSpace());
+        if (hadWord)
+            ret += cret;
+    }
+
+  okret:
+    if (err)
+        *err = SplitOk;
+    return ret;
+
+  quoteerr:
+    if (err)
+        *err = BadQuoting;
+    return QStringList();
+
+  metaerr:
+    if (err)
+        *err = FoundMeta;
+    return QStringList();
+}
+
+#endif // Q_OS_WIN
+
+inline static bool isSpecialCharUnix(ushort c)
+{
+    // Chars that should be quoted (TM). This includes:
+    static const uchar iqm[] = {
+        0xff, 0xff, 0xff, 0xff, 0xdf, 0x07, 0x00, 0xd8,
+        0x00, 0x00, 0x00, 0x38, 0x01, 0x00, 0x00, 0x78
+    }; // 0-32 \'"$`<>|;&(){}*?#!~[]
+
+    return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
+}
+
+inline static bool hasSpecialCharsUnix(const QString &arg)
+{
+    for (int x = arg.length() - 1; x >= 0; --x)
+        if (isSpecialCharUnix(arg.unicode()[x].unicode()))
+            return true;
+    return false;
+}
+
+QString QtcProcess::quoteArgUnix(const QString &arg)
+{
+    if (!arg.length())
+        return QString::fromLatin1("''");
+
+    QString ret(arg);
+    if (hasSpecialCharsUnix(ret)) {
+        ret.replace(QLatin1Char('\''), QLatin1String("'\\''"));
+        ret.prepend(QLatin1Char('\''));
+        ret.append(QLatin1Char('\''));
+    }
+    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)
+{
+    // Chars that should be quoted (TM). This includes:
+    // - control chars & space
+    // - the shell meta chars "&()<>^|
+    // - the potential separators ,;=
+    static const uchar iqm[] = {
+        0xff, 0xff, 0xff, 0xff, 0x45, 0x13, 0x00, 0x78,
+        0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x10
+    };
+
+    return (c < sizeof(iqm) * 8) && (iqm[c / 8] & (1 << (c & 7)));
+}
+
+inline static bool hasSpecialChars(const QString &arg)
+{
+    for (int x = arg.length() - 1; x >= 0; --x)
+        if (isSpecialChar(arg.unicode()[x].unicode()))
+            return true;
+    return false;
+}
+
+QString QtcProcess::quoteArg(const QString &arg)
+{
+    if (!arg.length())
+        return QString::fromLatin1("\"\"");
+
+    QString ret(arg);
+    if (hasSpecialChars(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".
+        ret.replace(QRegExp(QLatin1String("(\\\\*)\"")), QLatin1String("\"\\1\\1\\^\"\""));
+        // The argument must not end with a \ since this would be interpreted
+        // as escaping the quote -- rather put the \ behind the quote: e.g.
+        // rather use "foo"\ than "foo\"
+        ret.replace(QRegExp(QLatin1String("(\\\\*)$")), QLatin1String("\"\\1"));
+        ret.prepend(QLatin1Char('"'));
+    }
+    // FIXME: Without this, quoting is not foolproof. But it needs support in the process setup, etc.
+    //ret.replace('%', QLatin1String("%PERCENT_SIGN%"));
+    return ret;
+}
+
+void QtcProcess::addArg(QString *args, const QString &arg)
+{
+    if (!args->isEmpty())
+        *args += QLatin1Char(' ');
+    *args += quoteArg(arg);
+}
+
+QString QtcProcess::joinArgs(const QStringList &args)
+{
+    QString ret;
+    foreach (const QString &arg, args)
+        addArg(&ret, arg);
+    return ret;
+}
+#endif
+
+void QtcProcess::addArgs(QString *args, const QString &inArgs)
+{
+    if (!inArgs.isEmpty()) {
+        if (!args->isEmpty())
+            *args += QLatin1Char(' ');
+        *args += inArgs;
+    }
+}
+
+void QtcProcess::addArgs(QString *args, const QStringList &inArgs)
+{
+    foreach (const QString &arg, 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,
+                                const Environment *env, const QString *pwd)
+{
+    QtcProcess::SplitError err;
+    *outArgs = QtcProcess::prepareArgs(arguments, &err, 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);
+    }
+    return true;
+}
+#endif
+
+void QtcProcess::start()
+{
+    Environment env;
+    if (m_haveEnv) {
+        env = m_environment;
+        QProcess::setEnvironment(env.toStringList());
+    } else {
+        env = Environment::systemEnvironment();
+    }
+
+    const QString &workDir = workingDirectory();
+    QString command;
+#ifdef Q_OS_WIN
+    QString arguments;
+    prepareCommand(m_command, m_arguments, &command, &arguments, &env, &workDir);
+    setNativeArguments(arguments);
+    QProcess::start(command, QStringList());
+#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);
+#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)
+{
+    // 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".
+    const QChar bs(QLatin1Char('\\')), dq(QLatin1Char('"'));
+    for (int p = 0; p < ret.length(); p++) {
+        if (ret.at(p) == bs) {
+            bslashes++;
+        } else {
+            if (ret.at(p) == dq) {
+                if (bslashes) {
+                    ret.insert(p, QString(bslashes, bs));
+                    p += bslashes;
+                }
+                ret.insert(p, QLatin1String("\"\\^\""));
+                p += 4;
+            }
+            bslashes = 0;
+        }
+    }
+    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;
+Q_DECLARE_TYPEINFO(MxState, Q_PRIMITIVE_TYPE);
+
+// Pushed state for the case where a $(()) expansion turns out bogus
+typedef struct {
+    QString str;
+    int pos, varPos;
+} MxSave;
+Q_DECLARE_TYPEINFO(MxSave, Q_MOVABLE_TYPE);
+
+#include <QtCore/QStack>
+
+#endif
+
+// TODO: This documentation is relevant for end-users. Where to put it?
+/**
+ * Perform safe macro expansion (substitution) on a string for use
+ * in shell commands.
+ *
+ * \section Unix notes
+ *
+ * Explicitly supported shell constructs:
+ *   \\ '' "" {} () $(()) ${} $() ``
+ *
+ * Implicitly supported shell constructs:
+ *   (())
+ *
+ * Unsupported shell constructs that will cause problems:
+ * \list
+ * \li Shortened \c{case $v in pat)} syntax. Use \c{case $v in (pat)} instead.
+ * \li Bash-style \c{$""} and \c{$''} string quoting syntax.
+ * \endlist
+ *
+ * The rest of the shell (incl. bash) syntax is simply ignored,
+ * as it is not expected to cause problems.
+ *
+ * Security considerations:
+ * \list
+ * \li Backslash-escaping an expando is treated as a quoting error
+ * \li Do not put expandos into double quoted substitutions:
+ *     \badcode
+ *       "${VAR:-%{macro}}"
+ *     \endcode
+ * \li Do not put expandos into command line arguments which are nested
+ *     shell commands:
+ *     \badcode
+ *       sh -c 'foo \%{file}'
+ *     \endcode
+ *     \goodcode
+ *       file=\%{file} sh -c 'foo "$file"'
+ *     \endcode
+ * \endlist
+ *
+ * \section Windows notes
+ *
+ * All quoting syntax supported by splitArgs() is supported here as well.
+ * Additionally, command grouping via parentheses is recognized - note
+ * however, that the parser is much stricter about unquoted parentheses
+ * than cmd itself.
+ * The rest of the cmd syntax is simply ignored, as it is not expected
+ * to cause problems.
+ *
+ * Security considerations:
+ * \list
+ * \li Circumflex-escaping an expando is treated as a quoting error
+ * \li Closing double quotes right before expandos and opening double quotes
+ *     right after expandos are treated as quoting errors
+ * \li Do not put expandos into nested commands:
+ *     \badcode
+ *       for /f "usebackq" \%v in (`foo \%{file}`) do \@echo \%v
+ *     \endcode
+ * \li A macro's value must not contain anything which may be interpreted
+ *     as an environment variable expansion. A solution is replacing any
+ *     percent signs with a fixed string like \c{\%PERCENT_SIGN\%} and
+ *     injecting \c{PERCENT_SIGN=\%} into the shell's environment.
+ * \li Enabling delayed environment variable expansion (cmd /v:on) should have
+ *     no security implications, but may still wreak havoc due to the
+ *     need for doubling circumflexes if any exclamation marks are present,
+ *     and the need to circumflex-escape the exclamation marks themselves.
+ * \endlist
+ *
+ * \param cmd pointer to the string in which macros are expanded in-place
+ * \param mx pointer to a macro expander instance
+ * \return false if the string could not be parsed and therefore no safe
+ *   substitution was possible
+ */
+bool QtcProcess::expandMacros(QString *cmd, AbstractMacroExpander *mx)
+{
+    QString str = *cmd;
+    if (str.isEmpty())
+        return true;
+
+    QString rsts;
+    int varLen;
+    int varPos = 0;
+    if (!(varLen = mx->findMacro(str, &varPos, &rsts)))
+        return true;
+
+    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
+
+    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;
+                        }
+                        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);
+                    }
+                }
+            } 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.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 (!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;
+                        }
+                    } else if (crtState < CrtQuoted) {
+                        crtState = CrtInWord;
+                    }
+                    bslashes = 0;
+                    rbslashes = 0;
+                }
+            }
+            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;
+        }
+        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++;
+            }
+            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) {
+                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;
+        }
+        pos++;
+    }
+    // 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 ret = str;
+    expandMacros(&ret, mx);
+    return ret;
+}
+
+bool QtcProcess::ArgIterator::next()
+{
+    // We delay the setting of m_prev so we can still delete the last argument
+    // after we find that there are no more arguments. It's a bit of a hack ...
+    int prev = m_pos;
+
+    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 (crtState != CrtBasic) {
+                    // We'll lose shellQuote state here. Whatever ...
+                    goto doReturn;
+                }
+            } else {
+                if (cc == '\\') {
+                    bslashes++;
+                    if (crtState != CrtQuoted)
+                        crtState = CrtInWord;
+                    varState = NoVar;
+                } 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) {
+                            crtState = CrtInWord;
+                        }
+                    } 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;
+                    }
+                    for (; bslashes > 0; bslashes--)
+                        m_value += QLatin1Char('\\');
+                    m_value += QChar(cc);
+                }
+            }
+            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);
+                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 {
+                // 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;
+                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;
+            }
+#if 0 // MxGroup is impossible, see below.
+        } 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;
+#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)
+                continue;
+            if (sstack.isEmpty())
+                break;
+        }
+        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;
+    }
+    return false;
+#endif
+}
+
+void QtcProcess::ArgIterator::deleteArg()
+{
+    if (!m_prev)
+        while (m_pos < m_str->length() && m_str->at(m_pos).isSpace())
+            m_pos++;
+    m_str->remove(m_prev, m_pos - m_prev);
+    m_pos = m_prev;
+}
+
+void QtcProcess::ArgIterator::appendArg(const QString &str)
+{
+    const QString qstr = quoteArg(str);
+    if (!m_pos)
+        m_str->insert(0, qstr + QLatin1Char(' '));
+    else
+        m_str->insert(m_pos, QLatin1Char(' ') + qstr);
+    m_pos += qstr.length() + 1;
+}
diff --git a/src/libs/utils/qtcprocess.h b/src/libs/utils/qtcprocess.h
new file mode 100644
index 0000000000000000000000000000000000000000..e7439d8c27ca3c402bc8cc37791003483e965523
--- /dev/null
+++ b/src/libs/utils/qtcprocess.h
@@ -0,0 +1,149 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#ifndef QTCPROCESS_H
+#define QTCPROCESS_H
+
+#include <QProcess>
+
+#include "utils_global.h"
+
+#include "environment.h"
+
+namespace Utils {
+class AbstractMacroExpander;
+
+/*!
+  This class provides functionality for dealing with shell-quoted process arguments.
+*/
+class QTCREATOR_UTILS_EXPORT QtcProcess : public QProcess
+{
+    Q_OBJECT
+
+public:
+    QtcProcess(QObject *parent = 0) : QProcess(parent), m_haveEnv(false) {}
+    void setEnvironment(const Environment &env)
+        { m_environment = env; m_haveEnv = true; }
+    void setCommand(const QString &command, const QString &arguments)
+        { m_command = command; m_arguments = arguments; }
+    void start();
+
+    enum SplitError {
+        SplitOk = 0, //! All went just fine
+        BadQuoting, //! Command contains quoting errors
+        FoundMeta //! Command contains complex shell constructs
+    };
+
+    //! 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);
+    //! Quote a single argument and append it to a shell command
+    static void addArg(QString *args, const QString &arg);
+    //! Join an argument list into a shell command
+    static QString joinArgs(const QStringList &args);
+    //! 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);
+    //! 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,
+                               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,
+                                 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);
+
+    /*! Iterate over arguments from a command line.
+     *  Assumes that the name of the actual command is *not* part of the line.
+     *  Terminates after the first command if the command line is complex.
+     */
+    class QTCREATOR_UTILS_EXPORT ArgIterator {
+    public:
+        ArgIterator(QString *str) : m_str(str), m_pos(0), m_prev(-1) {}
+        //! 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.
+        bool isSimple() const { return m_simple; }
+        //! Return the string value of the current argument if it is simple, otherwise empty.
+        QString value() const { return m_value; }
+        //! Delete the last argument fetched via next() from the command line.
+        void deleteArg();
+        //! Insert argument into the command line after the last one fetched via next().
+        //! This may be used before the first call to next() to insert at the front.
+        void appendArg(const QString &str);
+    private:
+        QString *m_str, m_value;
+        int m_pos, m_prev;
+        bool m_simple;
+    };
+
+    class QTCREATOR_UTILS_EXPORT ConstArgIterator {
+    public:
+        ConstArgIterator(const QString &str) : m_str(str), m_ait(&m_str) {}
+        bool next() { return m_ait.next(); }
+        bool isSimple() const { return m_ait.isSimple(); }
+        QString value() const { return m_ait.value(); }
+    private:
+        QString m_str;
+        ArgIterator m_ait;
+    };
+
+private:
+    QString m_command;
+    QString m_arguments;
+    Environment m_environment;
+    bool m_haveEnv;
+};
+
+}
+
+#endif // QTCPROCESS_H
diff --git a/src/libs/utils/utils-lib.pri b/src/libs/utils/utils-lib.pri
index aa2e65cf11ddf99dbcad512e11b21b470fa95832..245bb7ecdd279fe7450ff5ddcb6b7c5adbbdc0c3 100644
--- a/src/libs/utils/utils-lib.pri
+++ b/src/libs/utils/utils-lib.pri
@@ -8,6 +8,7 @@ INCLUDEPATH += $$PWD
 QT += network
 
 SOURCES += $$PWD/environment.cpp \
+    $$PWD/qtcprocess.cpp \
     $$PWD/reloadpromptutils.cpp \
     $$PWD/stringutils.cpp \
     $$PWD/filesearch.cpp \
@@ -66,6 +67,7 @@ unix:!macx {
     SOURCES += $$PWD/unixutils.cpp
 }
 HEADERS += $$PWD/environment.h \
+    $$PWD/qtcprocess.h \
     $$PWD/utils_global.h \
     $$PWD/reloadpromptutils.h \
     $$PWD/stringutils.h \
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
index d67486117bc0aea443917fdc057bc4440db6869f..a041af7053314e4b725e2f1b66cd96cb0d55d98c 100644
--- a/tests/auto/auto.pro
+++ b/tests/auto/auto.pro
@@ -12,6 +12,7 @@ SUBDIRS += \
 #    icheckbuild \
 #    profilewriter \
     ioutils \
+    qtcprocess \
     utils_stringutils \
     filesearch
 
diff --git a/tests/auto/qtcprocess/qtcprocess.pro b/tests/auto/qtcprocess/qtcprocess.pro
new file mode 100644
index 0000000000000000000000000000000000000000..037015df4a72a1629b16aaa64a253145bd57e4be
--- /dev/null
+++ b/tests/auto/qtcprocess/qtcprocess.pro
@@ -0,0 +1,22 @@
+CONFIG += qtestlib testcase
+TEMPLATE = app
+CONFIG -= app_bundle
+DEFINES += QTCREATOR_UTILS_LIB
+
+UTILS_PATH = ../../../src/libs/utils
+
+INCLUDEPATH += $$UTILS_PATH/..
+DEPENDPATH += $$UTILS_PATH/..
+
+SOURCES += \
+    tst_qtcprocess.cpp \
+    $$UTILS_PATH/qtcprocess.cpp \
+    $$UTILS_PATH/stringutils.cpp \
+    $$UTILS_PATH/environment.cpp
+HEADERS += \
+    $$UTILS_PATH/qtcprocess.h \
+    $$UTILS_PATH/environment.h \
+    $$UTILS_PATH/stringutils.h \
+    $$UTILS_PATH/utils_global.h
+
+TARGET = tst_$$TARGET
diff --git a/tests/auto/qtcprocess/tst_qtcprocess.cpp b/tests/auto/qtcprocess/tst_qtcprocess.cpp
new file mode 100644
index 0000000000000000000000000000000000000000..a6ef552914902f84409f41bfba65429251e03623
--- /dev/null
+++ b/tests/auto/qtcprocess/tst_qtcprocess.cpp
@@ -0,0 +1,698 @@
+/**************************************************************************
+**
+** This file is part of Qt Creator
+**
+** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
+**
+** Contact: Nokia Corporation (qt-info@nokia.com)
+**
+** Commercial Usage
+**
+** Licensees holding valid Qt Commercial licenses may use this file in
+** accordance with the Qt Commercial License Agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Nokia.
+**
+** GNU Lesser General Public License Usage
+**
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file.  Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** If you are unsure which license is appropriate for your use, please
+** contact the sales department at http://qt.nokia.com/contact.
+**
+**************************************************************************/
+
+#include <utils/qtcprocess.h>
+#include <utils/stringutils.h>
+#include <utils/environment.h>
+
+#include <QtTest/QtTest>
+
+using namespace Utils;
+
+class MacroMapExpander : public AbstractQtcMacroExpander {
+public:
+    virtual bool resolveMacro(const QString &name, QString *ret)
+    {
+        QHash<QString, QString>::const_iterator it = m_map.constFind(name);
+        if (it != m_map.constEnd()) {
+            *ret = it.value();
+            return true;
+        }
+        return false;
+    }
+    void insert(const QString &key, const QString &value) { m_map.insert(key, value); }
+private:
+    QHash<QString, QString> m_map;
+};
+
+class tst_QtcProcess : public QObject
+{
+    Q_OBJECT
+
+private slots:
+    void initTestCase();
+    void splitArgs_data();
+    void splitArgs();
+    void prepareArgs_data();
+    void prepareArgs();
+    void prepareArgsEnv_data();
+    void prepareArgsEnv();
+    void expandMacros_data();
+    void expandMacros();
+    void iterations_data();
+    void iterations();
+    void iteratorEdits();
+
+private:
+    Environment env;
+    MacroMapExpander mx;
+};
+
+void tst_QtcProcess::initTestCase()
+{
+    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", "");
+}
+
+Q_DECLARE_METATYPE(QtcProcess::SplitError)
+
+void tst_QtcProcess::splitArgs_data()
+{
+    QTest::addColumn<QString>("in");
+    QTest::addColumn<QString>("out");
+    QTest::addColumn<QtcProcess::SplitError>("err");
+
+    static const struct {
+        const char * const in;
+        const char * const out;
+        const QtcProcess::SplitError err;
+    } 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 },
+#endif
+    };
+
+    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)
+                                  << vals[i].err;
+}
+
+void tst_QtcProcess::splitArgs()
+{
+    QFETCH(QString, in);
+    QFETCH(QString, out);
+    QFETCH(QtcProcess::SplitError, err);
+
+    QtcProcess::SplitError outerr;
+    QString outstr = QtcProcess::joinArgs(QtcProcess::splitArgs(in, false, &outerr));
+    QCOMPARE(outerr, err);
+    if (err == QtcProcess::SplitOk)
+        QCOMPARE(outstr, out);
+}
+
+void tst_QtcProcess::prepareArgs_data()
+{
+    QTest::addColumn<QString>("in");
+    QTest::addColumn<QString>("out");
+    QTest::addColumn<QtcProcess::SplitError>("err");
+
+    static const struct {
+        const char * const in;
+        const char * const out;
+        const QtcProcess::SplitError err;
+    } 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 },
+#endif
+    };
+
+    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)
+                                  << vals[i].err;
+}
+
+void tst_QtcProcess::prepareArgs()
+{
+    QFETCH(QString, in);
+    QFETCH(QString, out);
+    QFETCH(QtcProcess::SplitError, err);
+
+    QtcProcess::SplitError outerr;
+    QString outstr;
+#ifdef Q_OS_WIN
+    outstr = QtcProcess::prepareArgs(in, &outerr);
+#else
+    outstr = QtcProcess::joinArgs(QtcProcess::prepareArgs(in, &outerr));
+#endif
+    QCOMPARE(outerr, err);
+    if (err == QtcProcess::SplitOk)
+        QCOMPARE(outstr, out);
+}
+
+void tst_QtcProcess::prepareArgsEnv_data()
+{
+    QTest::addColumn<QString>("in");
+    QTest::addColumn<QString>("out");
+    QTest::addColumn<QtcProcess::SplitError>("err");
+
+    static const struct {
+        const char * const in;
+        const char * const out;
+        const QtcProcess::SplitError err;
+    } 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
+    };
+
+    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)
+                                  << vals[i].err;
+}
+
+void tst_QtcProcess::prepareArgsEnv()
+{
+    QFETCH(QString, in);
+    QFETCH(QString, out);
+    QFETCH(QtcProcess::SplitError, err);
+
+    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
+    QCOMPARE(outerr, err);
+    if (err == QtcProcess::SplitOk)
+        QCOMPARE(outstr, out);
+}
+
+void tst_QtcProcess::expandMacros_data()
+
+{
+    QTest::addColumn<QString>("in");
+    QTest::addColumn<QString>("out");
+    QChar sp(QLatin1Char(' '));
+
+    static const struct {
+        const char * const in;
+        const char * const out;
+    } 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\")\"" },
+        // 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
+    };
+
+    const char *title = 0;
+    for (unsigned i = 0; i < sizeof(vals)/sizeof(vals[0]); i++) {
+        if (!vals[i].out) {
+            title = vals[i].in;
+        } else {
+            char buf[80];
+            sprintf(buf, "%s: %s", title, vals[i].in);
+            QTest::newRow(buf) << QString::fromLatin1(vals[i].in)
+                               << QString::fromLatin1(vals[i].out);
+            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);
+        }
+    }
+}
+
+void tst_QtcProcess::expandMacros()
+{
+    QFETCH(QString, in);
+    QFETCH(QString, out);
+
+    QtcProcess::expandMacros(&in, &mx);
+    QCOMPARE(in, out);
+}
+
+void tst_QtcProcess::iterations_data()
+{
+    QTest::addColumn<QString>("in");
+    QTest::addColumn<QString>("out");
+
+    static const struct {
+        const char * const in;
+        const char * const out;
+    } 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
+    };
+
+    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);
+}
+
+void tst_QtcProcess::iterations()
+{
+    QFETCH(QString, in);
+    QFETCH(QString, out);
+
+    QString outstr;
+    for (QtcProcess::ArgIterator ait(&in); ait.next(); )
+        if (ait.isSimple())
+            QtcProcess::addArg(&outstr, ait.value());
+        else
+            QtcProcess::addArgs(&outstr, "{}");
+    QCOMPARE(outstr, out);
+}
+
+void tst_QtcProcess::iteratorEdits()
+{
+    QString in1 = "one two three", in2 = in1, in3 = in1, in4 = in1, in5 = in1;
+
+    QtcProcess::ArgIterator ait1(&in1);
+    QVERIFY(ait1.next());
+    ait1.deleteArg();
+    QVERIFY(ait1.next());
+    QVERIFY(ait1.next());
+    QVERIFY(!ait1.next());
+    QCOMPARE(in1, QString::fromLatin1("two three"));
+    ait1.appendArg("four");
+    QCOMPARE(in1, QString::fromLatin1("two three four"));
+
+    QtcProcess::ArgIterator ait2(&in2);
+    QVERIFY(ait2.next());
+    QVERIFY(ait2.next());
+    ait2.deleteArg();
+    QVERIFY(ait2.next());
+    ait2.appendArg("four");
+    QVERIFY(!ait2.next());
+    QCOMPARE(in2, QString::fromLatin1("one three four"));
+
+    QtcProcess::ArgIterator ait3(&in3);
+    QVERIFY(ait3.next());
+    ait3.appendArg("one-b");
+    QVERIFY(ait3.next());
+    QVERIFY(ait3.next());
+    ait3.deleteArg();
+    QVERIFY(!ait3.next());
+    QCOMPARE(in3, QString::fromLatin1("one one-b two"));
+
+    QtcProcess::ArgIterator ait4(&in4);
+    ait4.appendArg("pre-one");
+    QVERIFY(ait4.next());
+    QVERIFY(ait4.next());
+    QVERIFY(ait4.next());
+    ait4.deleteArg();
+    QVERIFY(!ait4.next());
+    QCOMPARE(in4, QString::fromLatin1("pre-one one two"));
+
+    QtcProcess::ArgIterator ait5(&in5);
+    QVERIFY(ait5.next());
+    QVERIFY(ait5.next());
+    QVERIFY(ait5.next());
+    QVERIFY(!ait5.next());
+    ait5.deleteArg();
+    QVERIFY(!ait5.next());
+    QCOMPARE(in5, QString::fromLatin1("one two"));
+}
+
+QTEST_MAIN(tst_QtcProcess)
+
+#include "tst_qtcprocess.moc"