OSDN Git Service

2a9ccafe643771ff718f69e9ead8b4f4c7821b64
[qt-creator-jp/qt-creator-jp.git] / src / libs / utils / buildablehelperlibrary.cpp
1 /**************************************************************************
2 **
3 ** This file is part of Qt Creator
4 **
5 ** Copyright (c) 2011 Nokia Corporation and/or its subsidiary(-ies).
6 **
7 ** Contact: Nokia Corporation (qt-info@nokia.com)
8 **
9 ** No Commercial Usage
10 **
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
14 ** this package.
15 **
16 ** GNU Lesser General Public License Usage
17 **
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.
24 **
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.
28 **
29 ** If you have questions regarding the use of this file, please contact
30 ** Nokia at qt-info@nokia.com.
31 **
32 **************************************************************************/
33
34 #include "buildablehelperlibrary.h"
35
36 #include <QtCore/QFileInfo>
37 #include <QtCore/QCoreApplication>
38 #include <QtCore/QHash>
39 #include <QtCore/QProcess>
40 #include <QtCore/QDir>
41 #include <QtCore/QDateTime>
42
43 #include <utils/environment.h>
44 #include <utils/synchronousprocess.h>
45
46 #include <QtGui/QDesktopServices>
47 #include <QtCore/QDebug>
48
49 namespace Utils {
50
51 QString BuildableHelperLibrary::findSystemQt(const Utils::Environment &env)
52 {
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);
60             if (qmake.exists()) {
61                 if (!qtVersionForQMake(qmake.absoluteFilePath()).isNull()) {
62                     return qmake.absoluteFilePath();
63                 }
64             }
65         }
66     }
67     return QString();
68 }
69
70 QString BuildableHelperLibrary::qtInstallDataDir(const QString &qmakePath)
71 {
72     QProcess proc;
73     proc.start(qmakePath, QStringList() << QLatin1String("-query") << QLatin1String("QT_INSTALL_DATA"));
74     if (proc.waitForFinished())
75         return QString(proc.readAll().trimmed());
76     return QString();
77 }
78
79 QString BuildableHelperLibrary::qtVersionForQMake(const QString &qmakePath)
80 {
81     if (qmakePath.isEmpty())
82         return QString();
83
84     QProcess qmake;
85     qmake.start(qmakePath, QStringList(QLatin1String("--version")));
86     if (!qmake.waitForStarted()) {
87         qWarning("Cannot start '%s': %s", qPrintable(qmakePath), qPrintable(qmake.errorString()));
88         return QString();
89     }
90     if (!qmake.waitForFinished())      {
91         Utils::SynchronousProcess::stopProcess(qmake);
92         qWarning("Timeout running '%s'.", qPrintable(qmakePath));
93         return QString();
94     }
95     if (qmake.exitStatus() != QProcess::NormalExit) {
96         qWarning("'%s' crashed.", qPrintable(qmakePath));
97         return QString();
98     }
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);
108         return version;
109     }
110     return QString();
111 }
112
113 QStringList BuildableHelperLibrary::possibleQMakeCommands()
114 {
115     // On windows no one has renamed qmake, right?
116 #ifdef Q_OS_WIN
117     return QStringList(QLatin1String("qmake.exe"));
118 #else
119     // On unix some distributions renamed qmake to avoid clashes
120     QStringList result;
121     result << QLatin1String("qmake-qt4") << QLatin1String("qmake4") << QLatin1String("qmake");
122     return result;
123 #endif
124 }
125
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)
131 {
132     if (!QDir().mkpath(targetDirectory)) {
133         *errorMessage = QCoreApplication::translate("ProjectExplorer::DebuggingHelperLibrary", "The target directory %1 could not be created.").arg(targetDirectory);
134         return false;
135     }
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())
142                 continue;
143             if (!QFile::remove(dest)) {
144                 *errorMessage = QCoreApplication::translate("ProjectExplorer::DebuggingHelperLibrary", "The existing file %1 could not be removed.").arg(destInfo.absoluteFilePath());
145                 return false;
146             }
147         }
148         if (!destInfo.dir().exists()) {
149             QDir().mkpath(destInfo.dir().absolutePath());
150         }
151
152         if (!QFile::copy(source, dest)) {
153             *errorMessage = QCoreApplication::translate("ProjectExplorer::DebuggingHelperLibrary", "The file %1 could not be copied to %2.").arg(source, dest);
154             return false;
155         }
156     }
157     return true;
158 }
159
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,
164                                     int timeoutMS,
165                                     bool ignoreNonNullExitCode,
166                                     QString *output, QString *errorMessage)
167 {
168     proc.start(binary, args);
169     if (!proc.waitForStarted()) {
170         *errorMessage = QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary",
171                                                     "Cannot start process: %1").
172                                                     arg(proc.errorString());
173         return false;
174     }
175     // Read stdout/err and check for timeouts
176     QByteArray stdOut;
177     QByteArray stdErr;
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);
183         return false;
184     }
185     if (proc.exitStatus() != QProcess::NormalExit) {
186         *errorMessage = QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary",
187                                                     "The process crashed.");
188         return false;
189     }
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);
195         return false;
196     }
197     output->append(stdOutS);
198     return true;
199 }
200
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,
205                             int timeoutMS,
206                             bool ignoreNonNullExitCode,
207                             QString *output, QString *errorMessage)
208 {
209     const bool rc = runBuildProcessI(proc, binary, args, timeoutMS, ignoreNonNullExitCode, output, errorMessage);
210     if (!rc) {
211         // Fail - reformat error.
212         QString cmd = binary;
213         if (!args.isEmpty()) {
214             cmd += QLatin1Char(' ');
215             cmd += args.join(QString(QLatin1Char(' ')));
216         }
217         *errorMessage =
218                 QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary",
219                                             "Error running '%1' in %2: %3").
220                                             arg(cmd, proc.workingDirectory(), *errorMessage);
221         qWarning("%s", qPrintable(*errorMessage));
222     }
223     return rc;
224 }
225
226
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)
233 {
234     const QChar newline = QLatin1Char('\n');
235     // Setup process
236     QProcess proc;
237     proc.setEnvironment(env.toStringList());
238     proc.setWorkingDirectory(directory);
239     proc.setProcessChannelMode(QProcess::MergedChannels);
240
241     output->append(QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary",
242                                           "Building helper '%1' in %2\n").arg(helperName, directory));
243     output->append(newline);
244
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);
250             return false;
251         }
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))
256             return false;
257     }
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;
265
266     output->append(newline);
267     output->append(QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary", "Running %1 %2 ...\n").arg(qmakeCommand,
268                                                                                                                      qmakeArgs.join(" ")));
269
270     if (!runBuildProcess(proc, qmakeCommand, qmakeArgs, 30000, false, output, errorMessage))
271         return false;
272     output->append(newline);
273     if (makeFullPath.isEmpty()) {
274         *errorMessage = QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary", "%1 not found in PATH\n").arg(makeCommand);
275         return false;
276     }
277     output->append(QCoreApplication::translate("ProjectExplorer::BuildableHelperLibrary", "Running %1 ...\n").arg(makeFullPath));
278     if (!runBuildProcess(proc, makeFullPath, QStringList(), 120000, false, output, errorMessage))
279         return false;
280     return true;
281 }
282
283 bool BuildableHelperLibrary::getHelperFileInfoFor(const QStringList &validBinaryFilenames,
284                                                   const QString &directory, QFileInfo* info)
285 {
286     if (!info)
287         return false;
288
289     foreach(const QString &binaryFilename, validBinaryFilenames) {
290         info->setFile(directory + binaryFilename);
291         if (info->exists())
292             return true;
293     }
294
295     return false;
296 }
297
298 QString BuildableHelperLibrary::byInstallDataHelper(const QString &sourcePath,
299                                                     const QStringList &sourceFileNames,
300                                                     const QStringList &installDirectories,
301                                                     const QStringList &validBinaryFilenames)
302 {
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;
309     }
310
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);
315
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
319     QFileInfo fileInfo;
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();
325             }
326         }
327     }
328     return newestHelper;
329 }
330
331 } // namespace Utils