diff options
author | Thiago Macieira <thiago.macieira@intel.com> | 2012-10-26 17:32:27 -0700 |
---|---|---|
committer | Thiago Macieira <thiago.macieira@intel.com> | 2012-12-19 09:02:11 +0100 |
commit | 49d2e10c94c1b66de12b05eeba163c6ec8db948c (patch) | |
tree | c52b0b8437cfe0823ea6880302e491cf27957e62 | |
parent | a2cfcef7a5369e2f130eaff7464aea1caed2119e (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-- | Makefile | 16 | ||||
-rw-r--r-- | src/qtchooser/Makefile | 45 | ||||
-rw-r--r-- | src/qtchooser/main.cpp | 371 | ||||
-rw-r--r-- | src/qtchooser/qtchooser.pro | 6 | ||||
-rw-r--r-- | tests/auto/auto.pro | 2 | ||||
-rw-r--r-- | tests/auto/qtchooser/qtchooser.pro | 11 | ||||
-rw-r--r-- | tests/auto/qtchooser/testdata/README | 12 | ||||
-rw-r--r-- | tests/auto/qtchooser/testdata/config1/4.8.conf | 2 | ||||
-rw-r--r-- | tests/auto/qtchooser/testdata/config1/empty.conf | 1 | ||||
-rw-r--r-- | tests/auto/qtchooser/testdata/config2/4.8.conf | 2 | ||||
-rw-r--r-- | tests/auto/qtchooser/testdata/config2/5.conf | 2 | ||||
-rw-r--r-- | tests/auto/qtchooser/testdata/config2/oneline.conf | 1 | ||||
-rw-r--r-- | tests/auto/qtchooser/testdata/default/default.conf | 2 | ||||
-rw-r--r-- | tests/auto/qtchooser/tst_qtchooser.cpp | 356 |
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" |