summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2012-10-26 17:32:27 -0700
committerThiago Macieira <thiago.macieira@intel.com>2012-12-19 09:02:11 +0100
commit49d2e10c94c1b66de12b05eeba163c6ec8db948c (patch)
treec52b0b8437cfe0823ea6880302e491cf27957e62
parenta2cfcef7a5369e2f130eaff7464aea1caed2119e (diff)
Long live the Qt tool chooser
This tool wraps the execution of the other tools. It's supposed to live on systems' /usr/bin dir or equivalent. WIP: need to compile on Mac, I need to know which libs are necessary for the FSFindFolder function (can't find the documentation on Apple's website). Change-Id: I1c429a159a4e02b78a835888d470514d8e4a23a7 Reviewed-by: Lars Knoll <lars.knoll@digia.com>
-rw-r--r--Makefile16
-rw-r--r--src/qtchooser/Makefile45
-rw-r--r--src/qtchooser/main.cpp371
-rw-r--r--src/qtchooser/qtchooser.pro6
-rw-r--r--tests/auto/auto.pro2
-rw-r--r--tests/auto/qtchooser/qtchooser.pro11
-rw-r--r--tests/auto/qtchooser/testdata/README12
-rw-r--r--tests/auto/qtchooser/testdata/config1/4.8.conf2
-rw-r--r--tests/auto/qtchooser/testdata/config1/empty.conf1
-rw-r--r--tests/auto/qtchooser/testdata/config2/4.8.conf2
-rw-r--r--tests/auto/qtchooser/testdata/config2/5.conf2
-rw-r--r--tests/auto/qtchooser/testdata/config2/oneline.conf1
-rw-r--r--tests/auto/qtchooser/testdata/default/default.conf2
-rw-r--r--tests/auto/qtchooser/tst_qtchooser.cpp356
14 files changed, 829 insertions, 0 deletions
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..06b1690
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+all:
+ cd src/qtchooser && $(MAKE)
+
+install:
+ cd src/qtchooser && $(MAKE) install
+
+uninstall:
+ cd src/qtchooser && $(MAKE) uninstall
+
+tests/auto/Makefile: tests/auto/auto.pro
+ cd tests/auto && qmake -o Makefile auto.pro
+
+check: all tests/auto/Makefile
+ cd tests/auto && $(MAKE) check
+
+.PHONY: all install uninstall check
diff --git a/src/qtchooser/Makefile b/src/qtchooser/Makefile
new file mode 100644
index 0000000..acd22fa
--- /dev/null
+++ b/src/qtchooser/Makefile
@@ -0,0 +1,45 @@
+####### Compiler, tools and options
+
+INSTALL_PROGRAM = install -m 755 -p
+DEL_FILE = rm -f
+CHK_DIR_EXISTS= test -d
+MKDIR = mkdir -p
+
+####### Files
+
+SOURCES = main.cpp
+OBJECTS = main.o
+TARGET = qtchooser
+
+
+first: all
+
+####### Build rules
+
+all: Makefile $(TARGET)
+
+$(TARGET): $(OBJECTS)
+ $(CXX) $(LFLAGS) -o $(TARGET) $(OBJECTS)
+
+clean:
+ -$(DEL_FILE) $(OBJECTS)
+ -$(DEL_FILE) *~ core *.core
+
+distclean: clean
+ -$(DEL_FILE) $(TARGET)
+ -$(DEL_FILE) Makefile
+
+
+####### Compile
+
+main.o: main.cpp
+ $(CXX) -c $(CXXFLAGS) $(INCPATH) -o main.o main.cpp
+
+####### Install
+
+install: FORCE
+
+uninstall: FORCE
+
+FORCE:
+
diff --git a/src/qtchooser/main.cpp b/src/qtchooser/main.cpp
new file mode 100644
index 0000000..a7b3599
--- /dev/null
+++ b/src/qtchooser/main.cpp
@@ -0,0 +1,371 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Intel Corporation.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the QtCore module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** 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.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+/*
+ * This tool is meant to wrap the other Qt tools by searching for different
+ * instances of Qt on the system. It's mostly destined to be used on Unix
+ * systems other than Mac OS X. For that reason, it uses POSIX APIs and does
+ * not try to be compatible with the DOS or Win32 APIs. In particular, it uses
+ * opendir(3) and readdir(3) as well as paths exclusively separated by slashes.
+ */
+
+#define _CRT_SECURE_NO_WARNINGS
+
+#include <set>
+#include <string>
+#include <vector>
+
+#include <errno.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+
+#if defined(_WIN32) || defined(__WIN32__)
+# include <process.h>
+# define execv _execv
+# define PATH_SEP "\\"
+#else
+# include <sys/types.h>
+# include <dirent.h>
+# include <libgen.h>
+# include <unistd.h>
+# define PATH_SEP "/"
+#endif
+
+using namespace std;
+
+static bool testMode = false;
+static const char *argv0;
+enum Mode {
+ RunTool,
+ ListVersions,
+ PrintEnvironment
+};
+
+struct Sdk
+{
+ string name;
+ string configFile;
+ string toolsPath;
+ string librariesPath;
+
+ bool isValid() const { return !toolsPath.empty(); }
+};
+
+struct ToolWrapper
+{
+ int listVersions();
+ int printEnvironment(const string &targetSdk);
+ int runTool(const string &targetSdk, const string &targetTool, char **argv);
+
+private:
+ vector<string> searchPaths() const;
+
+ typedef bool (ToolWrapper:: *VisitFunction)(const string &targetSdk, Sdk &item);
+ Sdk iterateSdks(const string &targetSdk, VisitFunction visit);
+ Sdk selectSdk(const string &targetSdk);
+
+ bool printSdk(const string &, Sdk &sdk);
+ bool matchSdk(const string &targetSdk, Sdk &sdk);
+};
+
+int ToolWrapper::listVersions()
+{
+ iterateSdks(string(), &ToolWrapper::printSdk);
+ return 0;
+}
+
+int ToolWrapper::printEnvironment(const string &targetSdk)
+{
+ Sdk sdk = selectSdk(targetSdk);
+ if (!sdk.isValid())
+ return 1;
+
+ // ### The paths and the name are not escaped, so hopefully no one will be
+ // installing Qt on paths containing backslashes or quotes on Unix. Paths
+ // with spaces are taken into account by surrounding the arguments with
+ // quotes.
+
+ printf("QT_SELECT=\"%s\"\n", sdk.name.c_str());
+ printf("QTTOOLDIR=\"%s\"\n", sdk.toolsPath.c_str());
+ printf("QTLIBDIR=\"%s\"\n", sdk.librariesPath.c_str());
+ return 0;
+}
+
+int ToolWrapper::runTool(const string &targetSdk, const string &targetTool, char **argv)
+{
+ Sdk sdk = selectSdk(targetSdk);
+ if (!sdk.isValid())
+ return 1;
+
+ string tool = sdk.toolsPath + PATH_SEP + targetTool;
+ argv[0] = &tool[0];
+ if (testMode) {
+ while (*argv)
+ printf("%s\n", *argv++);
+ return 0;
+ }
+
+ execv(argv[0], argv);
+ fprintf(stderr, "%s: could not exec '%s': %s\n",
+ argv0, argv[0], strerror(errno));
+ return 1;
+}
+
+static vector<string> stringSplit(const char *source)
+{
+#if defined(_WIN32) || defined(__WIN32__)
+ char listSeparator = ';';
+#else
+ char listSeparator = ':';
+#endif
+
+ vector<string> result;
+ if (!*source)
+ return result;
+
+ while (true) {
+ const char *p = strchr(source, listSeparator);
+ if (!p) {
+ result.push_back(source);
+ return result;
+ }
+
+ result.push_back(string(source, p - source));
+ source = p + 1;
+ }
+ return result;
+}
+
+vector<string> ToolWrapper::searchPaths() const
+{
+ vector<string> paths;
+ if (testMode) {
+ return stringSplit(getenv("QTCHOOSER_PATHS"));
+ } else {
+ // search the XDG config location directories
+ const char *globalDirs = getenv("XDG_CONFIG_DIRS");
+ paths = stringSplit(!globalDirs || !*globalDirs ? "/etc/xdg" : globalDirs);
+
+ string localDir;
+ const char *localDirEnv = getenv("XDG_CONFIG_HOME");
+ if (localDirEnv && *localDirEnv) {
+ localDir = localDirEnv;
+ } else {
+ localDir = getenv("HOME"); // accept empty $HOME too
+ localDir += "/.config";
+ }
+ paths.push_back(localDir);
+
+ for (vector<string>::iterator it = paths.begin(); it != paths.end(); ++it)
+ *it += "/qtchooser/";
+ }
+ return paths;
+}
+
+Sdk ToolWrapper::iterateSdks(const string &targetSdk, VisitFunction visit)
+{
+ vector<string> paths = searchPaths();
+ set<string> seenNames;
+ Sdk sdk;
+ for (vector<string>::iterator it = paths.begin(); it != paths.end(); ++it) {
+ const string &path = *it;
+
+ // no ISO C++ or ISO C API for listing directories, so use POSIX
+ DIR *dir = opendir(path.c_str());
+ if (!dir)
+ continue; // no such dir or not a dir, doesn't matter
+
+ while (struct dirent *d = readdir(dir)) {
+#ifdef _DIRENT_HAVE_D_TYPE
+ if (d->d_type == DT_DIR)
+ continue;
+#endif
+
+ static const char wantedSuffix[] = ".conf";
+ size_t fnamelen = strlen(d->d_name);
+ if (fnamelen < sizeof(wantedSuffix))
+ continue;
+ if (memcmp(d->d_name + fnamelen + 1 - sizeof(wantedSuffix), wantedSuffix, sizeof wantedSuffix - 1) != 0)
+ continue;
+
+ if (seenNames.find(d->d_name) != seenNames.end())
+ continue;
+
+ seenNames.insert(d->d_name);
+ sdk.name = d->d_name;
+ sdk.name.resize(fnamelen + 1 - sizeof wantedSuffix);
+ sdk.configFile = path + PATH_SEP + d->d_name;
+ if ((this->*visit)(targetSdk, sdk))
+ return sdk;
+ }
+
+ closedir(dir);
+ }
+ return Sdk();
+}
+
+Sdk ToolWrapper::selectSdk(const string &targetSdk)
+{
+ Sdk matchedSdk = iterateSdks(targetSdk, &ToolWrapper::matchSdk);
+ if (!matchedSdk.isValid()) {
+ fprintf(stderr, "%s: could not find a Qt installation of '%s'\n", argv0, targetSdk.c_str());
+ }
+ return matchedSdk;
+}
+
+bool ToolWrapper::printSdk(const string &, Sdk &sdk)
+{
+ printf("%s\n", sdk.name.c_str());
+ return false; // continue
+}
+
+bool ToolWrapper::matchSdk(const string &targetSdk, Sdk &sdk)
+{
+ if (targetSdk == sdk.name || (targetSdk.empty() && sdk.name == "default")) {
+ FILE *f = fopen(sdk.configFile.c_str(), "r");
+ if (!f) {
+ fprintf(stderr, "%s: could not open config file '%s': %s\n",
+ argv0, sdk.configFile.c_str(), strerror(errno));
+ exit(1);
+ }
+
+ // read the first two lines.
+ // 1) the first line contains the path to the Qt tools like qmake
+ // 2) the second line contains the path to the Qt libraries
+ // further lines are reserved for future enhancement
+ char buf[PATH_MAX];
+ if (!fgets(buf, PATH_MAX - 1, f)) {
+ fclose(f);
+ return false;
+ }
+ sdk.toolsPath = buf;
+ sdk.toolsPath.erase(sdk.toolsPath.size() - 1); // drop newline
+
+ if (!fgets(buf, PATH_MAX - 1, f)) {
+ fclose(f);
+ return false;
+ }
+ sdk.librariesPath = buf;
+ sdk.librariesPath.erase(sdk.librariesPath.size() - 1); // drop newline
+
+ fclose(f);
+ return true;
+ }
+
+ return false;
+}
+
+static inline bool beginsWith(const char *haystack, const char *needle)
+{
+ return strncmp(haystack, needle, strlen(needle)) == 0;
+}
+
+int main(int argc, char **argv)
+{
+ // search the environment for defaults
+ Mode operatingMode = RunTool;
+ argv0 = basename(argv[0]);
+ const char *targetSdk = getenv("QT_SELECT");
+ const char *targetTool = getenv("QTCHOOSER_RUNTOOL");
+
+#ifdef QTCHOOSER_TEST_MODE
+ testMode = atoi(getenv("QTCHOOSER_TESTMODE")) > 0;
+#endif
+
+ // if the target tool wasn't set in the environment, use argv[0]
+ if (!targetTool || !*targetTool)
+ targetTool = argv0;
+
+ // check the arguments to see if there's an override
+ int optind = 1;
+ for ( ; optind < argc; ++optind) {
+ char *arg = argv[optind];
+ if (*arg == '-') {
+ ++arg;
+ if (*arg == '-')
+ ++arg;
+ if (!*arg) {
+ // -- argument, ends our processing
+ // but skip this dash-dash argument
+ ++optind;
+ break;
+ } else if (beginsWith(arg, "qt")) {
+ // -qtX or -qt=X argument
+ arg += 2;
+ targetSdk = *arg == '=' ? arg + 1 : arg;
+ } else if (beginsWith(arg, "run-tool=")) {
+ // -run-tool= argument
+ targetTool = arg + strlen("run-tool=");
+ operatingMode = RunTool;
+ } else if (strcmp(arg, "list-versions") == 0) {
+ operatingMode = ListVersions;
+ } else if (beginsWith(arg, "print-env")) {
+ operatingMode = PrintEnvironment;
+ } else {
+ // not one of our arguments, must be for the target tool
+ break;
+ }
+ } else {
+ // not one of our arguments, must be for the target tool
+ break;
+ }
+ }
+
+ if (!targetSdk)
+ targetSdk = "";
+
+ ToolWrapper wrapper;
+
+ // dispatch
+ switch (operatingMode) {
+ case RunTool:
+ return wrapper.runTool(targetSdk,
+ targetTool,
+ argv + optind - 1);
+
+ case PrintEnvironment:
+ return wrapper.printEnvironment(targetSdk);
+
+ case ListVersions:
+ return wrapper.listVersions();
+ }
+}
diff --git a/src/qtchooser/qtchooser.pro b/src/qtchooser/qtchooser.pro
new file mode 100644
index 0000000..6037e5c
--- /dev/null
+++ b/src/qtchooser/qtchooser.pro
@@ -0,0 +1,6 @@
+TEMPLATE = app
+DESTDIR = ../../bin
+CONFIG -= qt
+SOURCES += main.cpp Makefile
+
+error("This .pro file is not meant to be used to build")
diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro
new file mode 100644
index 0000000..f132b8d
--- /dev/null
+++ b/tests/auto/auto.pro
@@ -0,0 +1,2 @@
+TEMPLATE = subdirs
+SUBDIRS = qtchooser
diff --git a/tests/auto/qtchooser/qtchooser.pro b/tests/auto/qtchooser/qtchooser.pro
new file mode 100644
index 0000000..de9bd46
--- /dev/null
+++ b/tests/auto/qtchooser/qtchooser.pro
@@ -0,0 +1,11 @@
+CONFIG += testcase
+CONFIG += parallel_test
+CONFIG -= app_bundle
+TARGET = tst_qtchooser
+requires(contains(QT_CONFIG,private_tests))
+
+QT -= gui
+QT += testlib
+
+SOURCES += tst_qtchooser.cpp
+DEFINES += SRCDIR=\\\"$$PWD/\\\"
diff --git a/tests/auto/qtchooser/testdata/README b/tests/auto/qtchooser/testdata/README
new file mode 100644
index 0000000..c0292cc
--- /dev/null
+++ b/tests/auto/qtchooser/testdata/README
@@ -0,0 +1,12 @@
+This dir contains the test data for the the tool wrapper. You can add more
+test directories for further tests, but be careful with config1 and config2.
+
+Contents:
+ config1/4.8.conf -> correct 4.8
+ config1/empty.conf -> malformed, ignored because it's empty
+ config2/4.8.conf -> incorrect 4.8, ignored because config1 is searched first
+ config2/5.conf -> correct Qt 5
+ config2/oneline.conf -> malformed, ignored
+ default/default.conf -> contains the "default" override
+
+The "default" directory is not part of most tests.
diff --git a/tests/auto/qtchooser/testdata/config1/4.8.conf b/tests/auto/qtchooser/testdata/config1/4.8.conf
new file mode 100644
index 0000000..54153b7
--- /dev/null
+++ b/tests/auto/qtchooser/testdata/config1/4.8.conf
@@ -0,0 +1,2 @@
+/correct-4.8/tooldir
+/correct-4.8/libdir
diff --git a/tests/auto/qtchooser/testdata/config1/empty.conf b/tests/auto/qtchooser/testdata/config1/empty.conf
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/tests/auto/qtchooser/testdata/config1/empty.conf
@@ -0,0 +1 @@
+
diff --git a/tests/auto/qtchooser/testdata/config2/4.8.conf b/tests/auto/qtchooser/testdata/config2/4.8.conf
new file mode 100644
index 0000000..71d3255
--- /dev/null
+++ b/tests/auto/qtchooser/testdata/config2/4.8.conf
@@ -0,0 +1,2 @@
+/wrong/qt-4.8/tooldir
+/wrong/qt-4.8/libdir
diff --git a/tests/auto/qtchooser/testdata/config2/5.conf b/tests/auto/qtchooser/testdata/config2/5.conf
new file mode 100644
index 0000000..dcb18d5
--- /dev/null
+++ b/tests/auto/qtchooser/testdata/config2/5.conf
@@ -0,0 +1,2 @@
+/qt5/tooldir
+/qt5/libdir
diff --git a/tests/auto/qtchooser/testdata/config2/oneline.conf b/tests/auto/qtchooser/testdata/config2/oneline.conf
new file mode 100644
index 0000000..bdf113b
--- /dev/null
+++ b/tests/auto/qtchooser/testdata/config2/oneline.conf
@@ -0,0 +1 @@
+/This file is too short
diff --git a/tests/auto/qtchooser/testdata/default/default.conf b/tests/auto/qtchooser/testdata/default/default.conf
new file mode 100644
index 0000000..4ad8426
--- /dev/null
+++ b/tests/auto/qtchooser/testdata/default/default.conf
@@ -0,0 +1,2 @@
+/default-qt/tooldir
+/default-qt/libdir
diff --git a/tests/auto/qtchooser/tst_qtchooser.cpp b/tests/auto/qtchooser/tst_qtchooser.cpp
new file mode 100644
index 0000000..85ec734
--- /dev/null
+++ b/tests/auto/qtchooser/tst_qtchooser.cpp
@@ -0,0 +1,356 @@
+/****************************************************************************
+**
+** Copyright (C) 2012 Intel Corporation.
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the Qt tool chooser of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** 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.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include <QtTest>
+
+#ifdef Q_OS_WIN
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h>
+#endif
+
+#ifdef Q_OS_WIN
+# define LIST_SEP ";"
+#else
+# define LIST_SEP ":"
+#endif
+
+#define VERIFY_NORMAL_EXIT(proc) \
+ QVERIFY(proc); \
+ QCOMPARE(proc->readAllStandardError().constData(), ""); \
+ QCOMPARE(proc->exitCode(), 0)
+
+class tst_ToolChooser : public QObject
+{
+ Q_OBJECT
+
+public:
+ QProcessEnvironment testModeEnvironment;
+ QString toolPath;
+ QString pathsWithDefault;
+
+ tst_ToolChooser();
+ inline QProcess *execute(const QStringList &arguments)
+ { return execute(arguments, testModeEnvironment); }
+ inline QProcess *execute(const QStringList &arguments, const QProcessEnvironment &env)
+ { return execute(toolPath, arguments, env); }
+ QProcess *execute(const QString &program, const QStringList &arguments, const QProcessEnvironment &env);
+
+ QString tempFileName() const;
+
+private Q_SLOTS:
+ void list();
+ void argv0_data();
+ void argv0();
+ void selectTool_data();
+ void selectTool();
+ void selectQt_data();
+ void selectQt();
+ void defaultQt_data();
+ void defaultQt();
+ void passArgs_data();
+ void passArgs();
+};
+
+tst_ToolChooser::tst_ToolChooser()
+ : testModeEnvironment(QProcessEnvironment::systemEnvironment())
+{
+ QString testData = QFINDTESTDATA("testdata");
+ pathsWithDefault = testData + "/config1" LIST_SEP +
+ testData + "/config2";
+ testModeEnvironment.insert("QTCHOOSER_PATHS", pathsWithDefault);
+ testModeEnvironment.insert("QTCHOOSER_TESTMODE", "1");
+ testModeEnvironment.insert("QT_SELECT", "4.8");
+
+ pathsWithDefault.prepend(testData + "/default" LIST_SEP);
+
+#ifdef Q_OS_WIN
+ toolPath = QCoreApplication::applicationDirPath() + "/../../../src/qtchooser/qtchooser.exe";
+#else
+ toolPath = QCoreApplication::applicationDirPath() + "/../../../src/qtchooser/qtchooser";
+#endif
+
+ QVERIFY(QFile::exists(toolPath));
+}
+
+QProcess *tst_ToolChooser::execute(const QString &program, const QStringList &arguments, const QProcessEnvironment &env)
+{
+ QProcess *proc = new QProcess;
+ proc->setProcessEnvironment(env);
+ proc->start(program, arguments, QIODevice::ReadOnly | QIODevice::Text);
+ if (!proc->waitForFinished()) {
+ QTest::qFail("Executing '" + program.toLocal8Bit()
+ + "' failed: " + proc->errorString().toLocal8Bit(),
+ __FILE__, __LINE__);
+ delete proc;
+ return 0;
+ }
+ return proc;
+}
+
+QString tst_ToolChooser::tempFileName() const
+{
+ static int seq = 0;
+ return QDir::currentPath() + "/tool" + QString::number(seq++) + '-' + QString::number(getpid()) + ".exe";
+}
+
+void tst_ToolChooser::list()
+{
+ QScopedPointer<QProcess> proc(execute(QStringList() << "-list-versions"));
+ VERIFY_NORMAL_EXIT(proc);
+
+ QStringList foundVersions;
+ while (!proc->atEnd()) {
+ QByteArray line = proc->readLine().trimmed();
+ QVERIFY(!line.isEmpty());
+ QVERIFY(!foundVersions.contains(line));
+ foundVersions << line;
+ }
+
+ QVERIFY(foundVersions.contains("4.8"));
+ QVERIFY(foundVersions.contains("5"));
+}
+
+void tst_ToolChooser::argv0_data()
+{
+ QTest::addColumn<bool>("symlink");
+ QTest::newRow("copy") << false;
+
+#ifdef Q_OS_UNIX
+ QTest::newRow("symlink") << true;
+#endif
+}
+
+void tst_ToolChooser::argv0()
+{
+ // check that the tool tries to execute something with the sane base name
+ // as its argv[0]
+ QFETCH(bool, symlink);
+
+ // We can't use QTemporaryFile here because we need to
+ // fully close the file in order to run the executable
+ QString tmpName = tempFileName();
+ QFile source(toolPath);
+ if (!symlink) {
+ QFile tmp(tmpName);
+ QVERIFY(tmp.open(QIODevice::ReadWrite));
+ QVERIFY(source.open(QIODevice::ReadOnly));
+ tmp.write(source.readAll());
+ tmp.setPermissions(QFile::ExeOwner | QFile::ReadOwner | QFile::WriteOwner);
+ source.close();
+ } else {
+#ifndef Q_OS_UNIX
+ qFatal("Impossible, cannot happen, you've broken the test!!");
+#else
+ // even though QTemporaryFile has the file opened, we'll overwrite it with a symlink
+ // on Unix, we're allowed to do that
+ QVERIFY(source.link(tmpName));
+#endif
+ }
+
+ QScopedPointer<QProcess> proc(execute(tmpName, QStringList(), testModeEnvironment));
+ VERIFY_NORMAL_EXIT(proc);
+
+ QByteArray procstdout = proc->readAllStandardOutput().trimmed();
+ QVERIFY2(procstdout.endsWith(QFileInfo(tmpName).fileName().toLocal8Bit()), procstdout);
+
+ QFile::remove(tmpName);
+}
+
+void tst_ToolChooser::selectTool_data()
+{
+ QTest::addColumn<bool>("useEnv");
+ QTest::newRow("cmdline") << false;
+ QTest::newRow("env") << true;
+}
+
+void tst_ToolChooser::selectTool()
+{
+ QFETCH(bool, useEnv);
+ QProcessEnvironment env = testModeEnvironment;
+ QStringList args;
+
+ if (useEnv)
+ env.insert("QTCHOOSER_RUNTOOL", "testtool");
+ else
+ args << "-run-tool=testtool";
+
+ QScopedPointer<QProcess> proc(execute(args, env));
+ VERIFY_NORMAL_EXIT(proc);
+
+ QByteArray procstdout = proc->readAllStandardOutput().trimmed();
+ QVERIFY2(procstdout.endsWith("testtool"), procstdout);
+}
+
+void tst_ToolChooser::selectQt_data()
+{
+ QTest::addColumn<bool>("useEnv");
+ QTest::addColumn<QString>("select");
+ QTest::addColumn<QString>("expected");
+
+ QTest::newRow("cmdline-4.8") << false << "4.8" << "correct-4.8";
+ QTest::newRow("env-4.8") << true << "4.8" << "correct-4.8";
+ QTest::newRow("cmdline-5") << false << "5" << "qt5";
+ QTest::newRow("env-5") << true << "5" << "qt5";
+
+ QTest::newRow("cmdline-invalid") << false << "invalid" << QString();
+ QTest::newRow("env-invalid") << true << "invalid" << QString();
+}
+
+void tst_ToolChooser::selectQt()
+{
+ QFETCH(bool, useEnv);
+ QFETCH(QString, select);
+ QFETCH(QString, expected);
+
+ QProcessEnvironment env = testModeEnvironment;
+ QStringList args;
+
+ env.remove("QT_SELECT");
+ if (useEnv)
+ env.insert("QT_SELECT", select);
+ else
+ args << "-qt=" + select;
+ args << "-print-env";
+
+ QScopedPointer<QProcess> proc(execute(args, env));
+ QVERIFY(proc);
+ if (expected.isEmpty()) {
+ // it is supposed to fail
+ QCOMPARE(proc->readAllStandardOutput().constData(), "");
+ QVERIFY(proc->exitCode() != 0);
+ QVERIFY(!proc->readAllStandardError().isEmpty());
+ } else {
+ QCOMPARE(proc->readAllStandardError().constData(), "");
+ QCOMPARE(proc->exitCode(), 0);
+
+ // The first line is QT_SELECT= again
+ QByteArray line = proc->readLine().trimmed();
+ QVERIFY2(line.startsWith("QT_SELECT="), line);
+
+ // The second line is QTTOOLDIR=
+ line = proc->readLine().trimmed();
+ QVERIFY2(line.startsWith("QTTOOLDIR="), line);
+ QVERIFY2(line.contains(expected.toLatin1()), line);
+ QVERIFY2(line.endsWith("tooldir\""), line);
+
+ // The third line is QTLIBDIR=
+ line = proc->readLine().trimmed();
+ QVERIFY2(line.startsWith("QTLIBDIR="), line);
+ QVERIFY2(line.contains(expected.toLatin1()), line);
+ QVERIFY2(line.endsWith("libdir\""), line);
+ }
+}
+
+void tst_ToolChooser::defaultQt_data()
+{
+ QTest::addColumn<bool>("withDefault");
+ QTest::addColumn<QString>("expected");
+
+ QTest::newRow("no-default") << false << QString();
+ QTest::newRow("with-default") << true << "default-qt";
+}
+
+void tst_ToolChooser::defaultQt()
+{
+ QFETCH(bool, withDefault);
+ QFETCH(QString, expected);
+
+ QProcessEnvironment env = testModeEnvironment;
+ env.remove("QT_SELECT");
+ if (withDefault)
+ env.insert("QTCHOOSER_PATHS", pathsWithDefault);
+
+ QScopedPointer<QProcess> proc(execute(QStringList() << "-print-qmake", env));
+ QVERIFY(proc);
+ if (withDefault) {
+ QCOMPARE(proc->readAllStandardError().constData(), "");
+ QCOMPARE(proc->exitCode(), 0);
+
+ QByteArray procstdout = proc->readAllStandardOutput().trimmed();
+ QVERIFY2(procstdout.contains(expected.toLatin1()), procstdout);
+ } else {
+ // no default, the tool fails
+ QVERIFY(proc->exitCode() != 0);
+ QVERIFY(!proc->readAllStandardError().isEmpty());
+ QByteArray procstdout = proc->readAllStandardOutput().trimmed();
+ QVERIFY2(procstdout.isEmpty(), procstdout.constData());
+ }
+}
+
+void tst_ToolChooser::passArgs_data()
+{
+ QTest::addColumn<QStringList>("args");
+ QTest::addColumn<QStringList>("expected");
+
+ QTest::newRow("empty") << QStringList() << QStringList();
+ QTest::newRow("drop1") << (QStringList() << "-qt5") << QStringList();
+ QTest::newRow("drop2") << (QStringList() << "-qt5" << "-qt5") << QStringList();
+
+ QTest::newRow("dash-dash") << (QStringList() << "--") << QStringList();
+ QTest::newRow("dash-dash-qt5") << (QStringList() << "--" << "-qt5") << (QStringList() << "-qt5");
+ QTest::newRow("dash-dash-dash-dash") << (QStringList() << "--" << "--") << (QStringList() << "--");
+
+ QTest::newRow("unknown-opt") << (QStringList() << "-query") << (QStringList() << "-query");
+ QTest::newRow("unknown-opt-qt5") << (QStringList() << "-query" << "-qt5") << (QStringList() << "-query" << "-qt5");
+ QTest::newRow("non-opt") << (QStringList() << ".") << (QStringList() << ".");
+ QTest::newRow("non-opt-qt5") << (QStringList() << "." << "-qt5") << (QStringList() << "." << "-qt5");
+}
+
+void tst_ToolChooser::passArgs()
+{
+ QFETCH(QStringList, args);
+ QFETCH(QStringList, expected);
+
+ QScopedPointer<QProcess> proc(execute(args));
+ VERIFY_NORMAL_EXIT(proc);
+
+ // skip the first line of procstdout, as it contains the tool name
+ proc->readLine();
+
+ QByteArray procstdout = proc->readAll().trimmed();
+ QCOMPARE(QString::fromLocal8Bit(procstdout), expected.join('\n'));
+}
+
+QTEST_MAIN(tst_ToolChooser)
+
+#include "tst_qtchooser.moc"