1 /**************************************************************************
3 ** This file is part of Qt Creator
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
11 ** This file contains pre-release code and may not be distributed.
12 ** You may use this file in accordance with the terms and conditions
13 ** contained in the Technology Preview License Agreement accompanying
16 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
25 ** In addition, as a special exception, Nokia gives you certain additional
26 ** rights. These rights are described in the Nokia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
32 **************************************************************************/
34 #include "buildablehelperlibrary.h"
36 #include <QtCore/QFileInfo>
37 #include <QtCore/QCoreApplication>
38 #include <QtCore/QHash>
39 #include <QtCore/QProcess>
40 #include <QtCore/QDir>
41 #include <QtCore/QDateTime>
43 #include <utils/environment.h>
44 #include <utils/synchronousprocess.h>
46 #include <QtGui/QDesktopServices>
47 #include <QtCore/QDebug>
51 QString BuildableHelperLibrary::findSystemQt(const Utils::Environment &env)
53 QStringList paths = env.path();
54 foreach (const QString &path, paths) {
55 QString prefix = path;
56 if (!prefix.endsWith(QLatin1Char('/')))
57 prefix.append(QLatin1Char('/'));
58 foreach (const QString &possibleCommand, possibleQMakeCommands()) {
59 const QFileInfo qmake(prefix + possibleCommand);
61 if (!qtVersionForQMake(qmake.absoluteFilePath()).isNull()) {
62 return qmake.absoluteFilePath();
70 QString BuildableHelperLibrary::qtInstallDataDir(const QString &qmakePath)
73 proc.start(qmakePath, QStringList() << QLatin1String("-query") << QLatin1String("QT_INSTALL_DATA"));
74 if (proc.waitForFinished())
75 return QString(proc.readAll().trimmed());
79 QString BuildableHelperLibrary::qtVersionForQMake(const QString &qmakePath)
81 if (qmakePath.isEmpty())
85 qmake.start(qmakePath, QStringList(QLatin1String("--version")));
86 if (!qmake.waitForStarted()) {
87 qWarning("Cannot start '%s': %s", qPrintable(qmakePath), qPrintable(qmake.errorString()));
90 if (!qmake.waitForFinished()) {
91 Utils::SynchronousProcess::stopProcess(qmake);
92 qWarning("Timeout running '%s'.", qPrintable(qmakePath));
95 if (qmake.exitStatus() != QProcess::NormalExit) {
96 qWarning("'%s' crashed.", qPrintable(qmakePath));
99 const QString output = QString::fromLocal8Bit(qmake.readAllStandardOutput());
100 static QRegExp regexp(QLatin1String("(QMake version|QMake version:)[\\s]*([\\d.]*)"),
101 Qt::CaseInsensitive);
102 regexp.indexIn(output);
103 if (regexp.cap(2).startsWith(QLatin1String("2."))) {
104 static QRegExp regexp2(QLatin1String("Using Qt version[\\s]*([\\d\\.]*)"),
105 Qt::CaseInsensitive);
106 regexp2.indexIn(output);
107 const QString version = regexp2.cap(1);
113 QStringList BuildableHelperLibrary::possibleQMakeCommands()
115 // On windows no one has renamed qmake, right?
117 return QStringList(QLatin1String("qmake.exe"));
119 // On unix some distributions renamed qmake to avoid clashes
121 result << QLatin1String("qmake-qt4") << QLatin1String("qmake4") << QLatin1String("qmake");
126 // Copy helper source files to a target directory, replacing older files.
127 bool BuildableHelperLibrary::copyFiles(const QString &sourcePath,
128 const QStringList &files,
129 const QString &targetDirectory,
130 QString *errorMessage)
132 if (!QDir().mkpath(targetDirectory)) {
133 *errorMessage = QCoreApplication::translate("ProjectExplorer::DebuggingHelperLibrary", "The target directory %1 could not be created.").arg(targetDirectory);
136 foreach (const QString &file, files) {
137 const QString source = sourcePath + file;
138 const QString dest = targetDirectory + file;
139 const QFileInfo destInfo(dest);
140 if (destInfo.exists()) {
141 if (destInfo.lastModified() >= QFileInfo(source).lastModified())
143 if (!QFile::remove(dest)) {
144 *errorMessage = QCoreApplication::translate("ProjectExplorer::DebuggingHelperLibrary", "The existing file %1 could not be removed.").arg(destInfo.absoluteFilePath());
148 if (!destInfo.dir().exists()) {
149 QDir().mkpath(destInfo.dir().absolutePath());
152 if (!QFile::copy(source, dest)) {
153 *errorMessage = QCoreApplication::translate("ProjectExplorer::DebuggingHelperLibrary", "The file %1 could not be copied to %2.").arg(source, dest);
160 // Helper: Run a build process with merged stdout/stderr
161 static inline bool runBuildProcessI(QProcess &proc,
162 const QString &binary,
163 const QStringList &args,
165 bool ignoreNonNullExitCode,
166 QString *output, QString *errorMessage)
168 proc.start(binary, args);
169 if (!proc.waitForStarted()) {
170 *errorMessage = QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary",
171 "Cannot start process: %1").
172 arg(proc.errorString());
175 // Read stdout/err and check for timeouts
178 if (!SynchronousProcess::readDataFromProcess(proc, timeoutMS, &stdOut, &stdErr, false)) {
179 *errorMessage = QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary",
180 "Timeout after %1s.").
181 arg(timeoutMS / 1000);
182 SynchronousProcess::stopProcess(proc);
185 if (proc.exitStatus() != QProcess::NormalExit) {
186 *errorMessage = QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary",
187 "The process crashed.");
190 const QString stdOutS = QString::fromLocal8Bit(stdOut);
191 if (!ignoreNonNullExitCode && proc.exitCode() != 0) {
192 *errorMessage = QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary",
193 "The process returned exit code %1:\n%2").
194 arg(proc.exitCode()).arg(stdOutS);
197 output->append(stdOutS);
201 // Run a build process with merged stdout/stderr and qWarn about errors.
202 static bool runBuildProcess(QProcess &proc,
203 const QString &binary,
204 const QStringList &args,
206 bool ignoreNonNullExitCode,
207 QString *output, QString *errorMessage)
209 const bool rc = runBuildProcessI(proc, binary, args, timeoutMS, ignoreNonNullExitCode, output, errorMessage);
211 // Fail - reformat error.
212 QString cmd = binary;
213 if (!args.isEmpty()) {
214 cmd += QLatin1Char(' ');
215 cmd += args.join(QString(QLatin1Char(' ')));
218 QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary",
219 "Error running '%1' in %2: %3").
220 arg(cmd, proc.workingDirectory(), *errorMessage);
221 qWarning("%s", qPrintable(*errorMessage));
227 bool BuildableHelperLibrary::buildHelper(const QString &helperName, const QString &proFilename,
228 const QString &directory, const QString &makeCommand,
229 const QString &qmakeCommand, const QString &mkspec,
230 const Utils::Environment &env, const QString &targetMode,
231 const QStringList &qmakeArguments, QString *output,
232 QString *errorMessage)
234 const QChar newline = QLatin1Char('\n');
237 proc.setEnvironment(env.toStringList());
238 proc.setWorkingDirectory(directory);
239 proc.setProcessChannelMode(QProcess::MergedChannels);
241 output->append(QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary",
242 "Building helper '%1' in %2\n").arg(helperName, directory));
243 output->append(newline);
245 const QString makeFullPath = env.searchInPath(makeCommand);
246 if (QFileInfo(directory + QLatin1String("/Makefile")).exists()) {
247 if (makeFullPath.isEmpty()) {
248 *errorMessage = QCoreApplication::translate("ProjectExplorer::DebuggingHelperLibrary",
249 "%1 not found in PATH\n").arg(makeCommand);
252 const QString cleanTarget = QLatin1String("distclean");
253 output->append(QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary",
254 "Running %1 %2...\n").arg(makeFullPath, cleanTarget));
255 if (!runBuildProcess(proc, makeFullPath, QStringList(cleanTarget), 30000, true, output, errorMessage))
258 QStringList qmakeArgs;
259 if (!targetMode.isEmpty())
260 qmakeArgs << targetMode;
261 if (!mkspec.isEmpty())
262 qmakeArgs << QLatin1String("-spec") << mkspec;
263 qmakeArgs << proFilename;
264 qmakeArgs << qmakeArguments;
266 output->append(newline);
267 output->append(QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary", "Running %1 %2 ...\n").arg(qmakeCommand,
268 qmakeArgs.join(" ")));
270 if (!runBuildProcess(proc, qmakeCommand, qmakeArgs, 30000, false, output, errorMessage))
272 output->append(newline);
273 if (makeFullPath.isEmpty()) {
274 *errorMessage = QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary", "%1 not found in PATH\n").arg(makeCommand);
277 output->append(QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary", "Running %1 ...\n").arg(makeFullPath));
278 if (!runBuildProcess(proc, makeFullPath, QStringList(), 120000, false, output, errorMessage))
283 bool BuildableHelperLibrary::getHelperFileInfoFor(const QStringList &validBinaryFilenames,
284 const QString &directory, QFileInfo* info)
289 foreach(const QString &binaryFilename, validBinaryFilenames) {
290 info->setFile(directory + binaryFilename);
298 QString BuildableHelperLibrary::byInstallDataHelper(const QString &sourcePath,
299 const QStringList &sourceFileNames,
300 const QStringList &installDirectories,
301 const QStringList &validBinaryFilenames)
303 // find the latest change to the sources
304 QDateTime sourcesModified;
305 foreach (const QString &sourceFileName, sourceFileNames) {
306 const QDateTime fileModified = QFileInfo(sourcePath + sourceFileName).lastModified();
307 if (fileModified.isValid() && (!sourcesModified.isValid() || fileModified > sourcesModified))
308 sourcesModified = fileModified;
311 // We pretend that the lastmodified of gdbmacros.cpp is 5 minutes before what the file system says
312 // Because afer a installation from the package the modified dates of gdbmacros.cpp
313 // and the actual library are close to each other, but not deterministic in one direction
314 sourcesModified = sourcesModified.addSecs(-300);
316 // look for the newest helper library in the different locations
317 QString newestHelper;
318 QDateTime newestHelperModified = sourcesModified; // prevent using one that's older than the sources
320 foreach(const QString &installDirectory, installDirectories) {
321 if (getHelperFileInfoFor(validBinaryFilenames, installDirectory, &fileInfo)) {
322 if (fileInfo.lastModified() > newestHelperModified) {
323 newestHelper = fileInfo.filePath();
324 newestHelperModified = fileInfo.lastModified();