summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorThiago Macieira <thiago.macieira@intel.com>2014-02-24 14:57:23 -0800
committerThiago Macieira <thiago.macieira@intel.com>2014-06-13 17:19:06 +0200
commitd2b79979be5726191664c3a2fdc09295ae4ea8e1 (patch)
treefb71ee0a3828fe985bc4ee72889e144e033b20aa
parentc42ed38dd8eae38316846a0de5e886defc2ef4c0 (diff)
Add the install mode for qtchooser
Change-Id: I8bbd7fc683e341ca6af3b1396a655684233f5562 Reviewed-by: Shawn Rutledge <shawn.rutledge@digia.com> Reviewed-by: Frederik Gladhorn <frederik.gladhorn@digia.com>
-rw-r--r--src/qtchooser/main.cpp221
-rw-r--r--tests/auto/qtchooser/testdata/config1/qtchooser/later.conf2
-rw-r--r--tests/auto/qtchooser/tst_qtchooser.cpp128
3 files changed, 309 insertions, 42 deletions
diff --git a/src/qtchooser/main.cpp b/src/qtchooser/main.cpp
index cf84a51..5e6e9cb 100644
--- a/src/qtchooser/main.cpp
+++ b/src/qtchooser/main.cpp
@@ -60,6 +60,7 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
+#include <limits.h>
#if defined(_WIN32) || defined(__WIN32__)
# include <process.h>
@@ -68,7 +69,9 @@
# define EXE_SUFFIX ".exe"
#else
# include <sys/types.h>
+# include <sys/stat.h>
# include <dirent.h>
+# include <fcntl.h>
# include <libgen.h>
# include <pwd.h>
# include <unistd.h>
@@ -87,7 +90,17 @@ enum Mode {
PrintHelp,
RunTool,
ListVersions,
- PrintEnvironment
+ PrintEnvironment,
+ Install
+};
+
+enum InstallOptions
+{
+ GlobalInstall = 0,
+ LocalInstall = 1,
+
+ NoOverwrite = 0,
+ ForceOverwrite = 2
};
struct Sdk
@@ -106,6 +119,7 @@ struct ToolWrapper
int listVersions();
int printEnvironment(const string &targetSdk);
int runTool(const string &targetSdk, const string &targetTool, char **argv);
+ int install(const string &sdkName, const string &qmake, int installOptions);
private:
vector<string> searchPaths() const;
@@ -123,6 +137,7 @@ int ToolWrapper::printHelp()
{
puts("Usage:\n"
" qtchooser { -l | -list-versions | -print-env }\n"
+ " qtchooser -install [-f] [-local] <name> <path-to-qmake>\n"
" qtchooser -run-tool=<tool name> [-qt=<Qt version>] [program arguments]\n"
" <executable name> [-qt=<Qt version>] [program arguments]\n"
"\n"
@@ -204,6 +219,51 @@ bool linksBackToSelf(const char *link, const char *target)
return false;
}
+static bool readLine(FILE *f, string *result)
+{
+#if _POSIX_VERSION >= 200809L
+ size_t len = 0;
+ char *line = 0;
+ ssize_t read = getline(&line, &len, f);
+ if (read < 0) {
+ free(line);
+ return false;
+ }
+
+ line[strlen(line) - 1] = '\0';
+ *result = line;
+ free(line);
+#elif defined(PATH_MAX)
+ char buf[PATH_MAX];
+ if (!fgets(buf, PATH_MAX - 1, f))
+ return false;
+
+ buf[PATH_MAX - 1] = '\0';
+ buf[strlen(buf) - 1] = '\0';
+ *result = buf;
+#else
+# error "POSIX < 2008 and no PATH_MAX, fix me"
+#endif
+ return true;
+}
+
+static bool mkparentdir(string name)
+{
+ // create the dir containing this dir
+ size_t pos = name.rfind('/');
+ if (pos == string::npos)
+ return false;
+ name.erase(pos);
+ if (mkdir(name.c_str(), 0777) == -1) {
+ if (errno != ENOENT)
+ return false;
+ // try this dir's parent too
+ if (!mkparentdir(name))
+ return false;
+ }
+ return true;
+}
+
int ToolWrapper::runTool(const string &targetSdk, const string &targetTool, char **argv)
{
Sdk sdk = selectSdk(targetSdk);
@@ -240,6 +300,105 @@ int ToolWrapper::runTool(const string &targetSdk, const string &targetTool, char
#endif
}
+static const char *to_number(int number)
+{
+ // obviously not thread-safe
+ static char buffer[sizeof "2147483647"];
+ snprintf(buffer, sizeof buffer, "%d", number);
+ return buffer;
+}
+
+int ToolWrapper::install(const string &sdkName, const string &qmake, int installOptions)
+{
+ if (qmake.size() == 0) {
+ fprintf(stderr, "%s: missing option: path to qmake\n", argv0);
+ return 1;
+ }
+
+ if ((installOptions & ForceOverwrite) == 0) {
+ Sdk matchedSdk = iterateSdks(sdkName, &ToolWrapper::matchSdk);
+ if (matchedSdk.isValid()) {
+ fprintf(stderr, "%s: SDK \"%s\" already exists\n", argv0, sdkName.c_str());
+ return 1;
+ }
+ }
+
+ // first of all, get the bin and lib dirs from qmake
+ string bindir, libdir;
+ FILE *bin, *lib;
+ bin = popen(("'" + qmake + "' -query QT_INSTALL_BINS").c_str(), "r");
+ lib = popen(("'" + qmake + "' -query QT_INSTALL_LIBS").c_str(), "r");
+
+ if (!readLine(bin, &bindir) || !readLine(lib, &libdir)\
+ || pclose(bin) == -1 || pclose(lib) == -1) {
+ fprintf(stderr, "%s: error running %s: %s\n", argv0, qmake.c_str(), strerror(errno));
+ return 1;
+ }
+
+ const string sdkFileName = sdkName + confSuffix;
+ const string fileContents = bindir + "\n" + libdir + "\n";
+ string sdkFullPath;
+
+ // get the list of paths to try and install the SDK on the first we are able to;
+ // since the list is sorted in search order, we need to try in the reverse order
+ const vector<string> paths = searchPaths();
+ vector<string>::const_iterator it = paths.end();
+ vector<string>::const_iterator prev = it - 1;
+ for ( ; it != paths.begin(); it = prev--) {
+ sdkFullPath = *prev + sdkFileName;
+
+ // are we trying to install here?
+ bool installHere = (installOptions & LocalInstall) == 0 || prev == paths.begin();
+ if (!installHere)
+ continue;
+
+#ifdef QTCHOOSER_TEST_MODE
+ puts(sdkFullPath.c_str());
+ puts(fileContents.c_str());
+ return 0;
+#else
+ // we're good, create the SDK name
+ string tempname = sdkFullPath + "." + to_number(rand());
+ int fd;
+ // create a temporary file here
+ while (true) {
+ fd = ::open(tempname.c_str(), O_WRONLY | O_CREAT | O_EXCL, 0666);
+ if (fd == -1 && errno == EEXIST)
+ continue;
+ if (fd == -1 && errno == ENOENT) {
+ // could be because the dir itself doesn't exist
+ if (mkparentdir(tempname))
+ continue;
+ }
+ break;
+ }
+ if (fd == -1)
+ continue;
+
+ size_t bytesWritten = 0;
+ while (bytesWritten < fileContents.size()) {
+ ssize_t written = ::write(fd, fileContents.data() + bytesWritten, fileContents.size() - bytesWritten);
+ if (written == -1) {
+ fprintf(stderr, "%s: error writing to \"%s\": %s\n", argv0, tempname.c_str(), strerror(errno));
+ ::close(fd);
+ return 1;
+ }
+
+ bytesWritten += written;
+ }
+
+ // atomic rename
+ ::close(fd);
+ if (rename(tempname.c_str(), sdkFullPath.c_str()) == 0)
+ return 0; // success
+#endif
+ }
+
+ // if we got here, we failed to create the file
+ fprintf(stderr, "%s: could not create SDK: %s: %s\n", argv0, sdkFullPath.c_str(), strerror(errno));
+ return 1;
+}
+
static vector<string> stringSplit(const char *source)
{
#if defined(_WIN32) || defined(__WIN32__)
@@ -377,44 +536,10 @@ bool ToolWrapper::matchSdk(const string &targetSdk, Sdk &sdk)
// 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
-#if _POSIX_VERSION >= 200809L
- size_t len = 0;
- char *line = 0;
- ssize_t read = getline(&line, &len, f);
- if (read < 0) {
- free(line);
+ if (!readLine(f, &sdk.toolsPath) || !readLine(f, &sdk.librariesPath)) {
fclose(f);
return false;
}
- sdk.toolsPath = line;
-
- read = getline(&line, &len, f);
- if (read < 0) {
- free(line);
- fclose(f);
- return false;
- }
- sdk.librariesPath = line;
-
- free(line);
-#elif defined(PATH_MAX)
- char buf[PATH_MAX];
- if (!fgets(buf, PATH_MAX - 1, f)) {
- fclose(f);
- return false;
- }
- sdk.toolsPath = buf;
-
- if (!fgets(buf, PATH_MAX - 1, f)) {
- fclose(f);
- return false;
- }
- sdk.librariesPath = buf;
-#else
-# error "POSIX < 2008 and no PATH_MAX, fix me"
-#endif
- sdk.toolsPath.erase(sdk.toolsPath.size() - 1); // drop newline
- sdk.librariesPath.erase(sdk.librariesPath.size() - 1); // drop newline
fclose(f);
return true;
@@ -487,6 +612,9 @@ int main(int argc, char **argv)
// running qtchooser itself
// check for our arguments
operatingMode = PrintHelp;
+ int installOptions = 0;
+ string sdkName;
+ string qmakePath;
for ( ; optind < argc; ++optind) {
char *arg = argv[optind];
if (*arg == '-') {
@@ -496,14 +624,30 @@ int main(int argc, char **argv)
// double-dash arguments are OK too
if (*arg == '-')
++arg;
- if (strcmp(arg, "list-versions") == 0 || strcmp(arg, "l") == 0) {
+ if (strcmp(arg, "install") == 0) {
+ operatingMode = Install;
+ } else if (operatingMode == Install && (strcmp(arg, "force") == 0 || strcmp(arg, "f") == 0)) {
+ installOptions |= ForceOverwrite;
+ } else if (strcmp(arg, "list-versions") == 0 || strcmp(arg, "l") == 0) {
operatingMode = ListVersions;
+ } else if (operatingMode == Install && strcmp(arg, "local") == 0) {
+ installOptions |= LocalInstall;
} else if (beginsWith(arg, "print-env")) {
operatingMode = PrintEnvironment;
} else if (strcmp(arg, "help") != 0) {
fprintf(stderr, "%s: unknown option: %s\n", argv0, arg - 1);
return 1;
}
+ } else if (operatingMode == Install) {
+ if (qmakePath.size()) {
+ fprintf(stderr, "%s: install mode takes exactly two arguments; unknown option: %s\n", argv0, arg);
+ return 1;
+ }
+ if (sdkName.size()) {
+ qmakePath = arg;
+ } else {
+ sdkName = strlen(arg) ? arg : "default";
+ }
} else {
fprintf(stderr, "%s: unknown argument: %s\n", argv0, arg);
return 1;
@@ -526,5 +670,8 @@ int main(int argc, char **argv)
case ListVersions:
return wrapper.listVersions();
+
+ case Install:
+ return wrapper.install(sdkName, qmakePath, installOptions);
}
}
diff --git a/tests/auto/qtchooser/testdata/config1/qtchooser/later.conf b/tests/auto/qtchooser/testdata/config1/qtchooser/later.conf
new file mode 100644
index 0000000..cb77758
--- /dev/null
+++ b/tests/auto/qtchooser/testdata/config1/qtchooser/later.conf
@@ -0,0 +1,2 @@
+/later/tooldir
+/later/libdir
diff --git a/tests/auto/qtchooser/tst_qtchooser.cpp b/tests/auto/qtchooser/tst_qtchooser.cpp
index f1b3b5f..8864130 100644
--- a/tests/auto/qtchooser/tst_qtchooser.cpp
+++ b/tests/auto/qtchooser/tst_qtchooser.cpp
@@ -57,9 +57,9 @@
#endif
#define VERIFY_NORMAL_EXIT(proc) \
- if (!proc) return; \
- QCOMPARE(proc->readAllStandardError().constData(), ""); \
- QCOMPARE(proc->exitCode(), 0)
+ if (!(proc)) return; \
+ QCOMPARE((proc)->readAllStandardError().constData(), ""); \
+ QCOMPARE((proc)->exitCode(), 0)
class tst_ToolChooser : public QObject
{
@@ -73,6 +73,7 @@ public:
CommandLine = 0x8
};
QProcessEnvironment testModeEnvironment;
+ QString testData;
QString toolPath;
QString pathsWithDefault;
QString tempFileName;
@@ -100,15 +101,18 @@ private Q_SLOTS:
void defaultQt();
void passArgs_data();
void passArgs();
+ void install_data();
+ void install();
+ void install2();
};
tst_ToolChooser::tst_ToolChooser()
: testModeEnvironment(QProcessEnvironment::systemEnvironment())
{
#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0)
- QString testData = QFINDTESTDATA("testdata");
+ testData = QFINDTESTDATA("testdata");
#else
- QString testData = SRCDIR "testdata";
+ testData = SRCDIR "testdata";
#endif
pathsWithDefault = testData + "/config1" LIST_SEP +
testData + "/config2";
@@ -374,6 +378,120 @@ void tst_ToolChooser::passArgs()
QCOMPARE(QString::fromLocal8Bit(procstdout), expected.join("\n"));
}
+void tst_ToolChooser::install_data()
+{
+ QTest::addColumn<QStringList>("args");
+ QTest::addColumn<QString>("expectedName");
+
+ QTest::newRow("missing-name") << QStringList() << QString();
+ QTest::newRow("missing-qmake") << (QStringList() << "sdk") << QString();
+
+ QString qmake = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmake";
+ QVERIFY(QFile::exists(qmake));
+
+ QStringList baseArgs;
+ baseArgs << "5" << qmake;
+
+ QTest::newRow("global-wouldoverwrite") << baseArgs << QString();
+ QTest::newRow("local-wouldoverwrite") << (QStringList() << "-local" << baseArgs) << QString();
+
+ baseArgs.prepend("-f");
+ QTest::newRow("global-overwrite") << baseArgs << testData + "/config2/qtchooser/5.conf";
+ QTest::newRow("local-overwrite") << (QStringList() << "-local" << baseArgs) << "/dev/null/qtchooser/5.conf";
+
+ baseArgs.clear();
+ baseArgs << "newname" << qmake;
+ QTest::newRow("global-newname") << baseArgs << testData + "/config2/qtchooser/newname.conf";
+ QTest::newRow("local-newname") << (QStringList() << "-local" << baseArgs) << "/dev/null/qtchooser/newname.conf";
+
+ // ensure that we find an SDK in a later path, even if we could install on an earlier one
+ QTest::newRow("global-wouldoverwrite-later") << (QStringList() << "later" << qmake) << QString();
+}
+
+void tst_ToolChooser::install()
+{
+ QFETCH(QStringList, args);
+ QFETCH(QString, expectedName);
+
+ QProcessEnvironment env = testModeEnvironment;
+ QScopedPointer<QProcess> proc(execute((QStringList() << "-install") + args, env));
+ QVERIFY(!!proc);
+ if (expectedName.isEmpty()) {
+ QByteArray err = proc->readAllStandardError();
+ QVERIFY(!err.isEmpty());
+ QVERIFY(proc->exitCode() != 0);
+ qDebug() << err.trimmed();
+ } else {
+ VERIFY_NORMAL_EXIT(proc);
+
+ QByteArray out = proc->readLine();
+ QVERIFY(!out.isEmpty());
+ QCOMPARE(QString(out).trimmed(), expectedName);
+
+ out = proc->readLine();
+ QCOMPARE(QString(out).trimmed(), QLibraryInfo::location(QLibraryInfo::BinariesPath));
+
+ out = proc->readLine();
+ QCOMPARE(QString(out).trimmed(), QLibraryInfo::location(QLibraryInfo::LibrariesPath));
+ }
+}
+
+void tst_ToolChooser::install2()
+{
+ // verify that the root is not writable by the current user
+ QString root = QDir::rootPath();
+ {
+ QFile f(root + "qtchooser");
+ QVERIFY(!f.exists());
+ QVERIFY(!f.open(QIODevice::ReadWrite));
+ }
+
+ QTemporaryDir tempdir;
+ QDir dir(tempdir.path());
+ dir.mkdir("/global");
+ dir.mkdir("/home");
+
+ QString realToolPath = QCoreApplication::applicationDirPath() + "/../../../src/qtchooser/qtchooser" EXE_SUFFIX;
+ QProcessEnvironment env = QProcessEnvironment::systemEnvironment();
+ env.remove("XDG_CONFIG_HOME");
+ env.insert("XDG_CONFIG_DIRS", tempdir.path() + "/global/etc/xdg" LIST_SEP "/");
+ env.insert("HOME", tempdir.path() + "/home");
+
+ QProcess proc;
+ proc.setProcessEnvironment(env);
+ QString qmake = QLibraryInfo::location(QLibraryInfo::BinariesPath) + "/qmake";
+ QString expectedContents = QLibraryInfo::location(QLibraryInfo::BinariesPath) + '\n' +
+ QLibraryInfo::location(QLibraryInfo::LibrariesPath) + '\n';
+
+ // test 1: check that it installs into $HOME and recursively mkdirs
+ proc.setProgram(realToolPath);
+ proc.setArguments(QStringList() << "-install" << "-local" << "test" << qmake);
+ proc.start();
+ QVERIFY(proc.waitForFinished());
+ VERIFY_NORMAL_EXIT(&proc);
+
+ // find the file it must've created
+ {
+ QFile f(tempdir.path() + "/home/.config/qtchooser/test.conf");
+ QVERIFY2(f.open(QIODevice::ReadOnly), qPrintable(f.errorString()));
+ QCOMPARE(f.readAll(), expectedContents.toLocal8Bit());
+ }
+ QVERIFY(!QFile::exists(tempdir.path() + "/global/etc/xdg/qtchooser/test.conf"));
+
+ // test 2: check that it can create a global override
+ proc.setArguments(QStringList() << "-install" << "-f" << "test" << qmake);
+ proc.start();
+ QVERIFY(proc.waitForFinished());
+ VERIFY_NORMAL_EXIT(&proc);
+
+ // find the global file
+ {
+ QFile f(tempdir.path() + "/global/etc/xdg/qtchooser/test.conf");
+ QVERIFY2(f.open(QIODevice::ReadOnly), qPrintable(f.errorString()));
+ QCOMPARE(f.readAll(), expectedContents.toLocal8Bit());
+ }
+}
+
QTEST_MAIN(tst_ToolChooser)
#include "tst_qtchooser.moc"