source: trunk/tools/configure/environment.cpp@ 1077

Last change on this file since 1077 was 846, checked in by Dmitry A. Kuminov, 14 years ago

trunk: Merged in qt 4.7.2 sources from branches/vendor/nokia/qt.

File size: 19.6 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the tools applications of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "environment.h"
43
44#include <process.h>
45#include <iostream>
46#include <qdebug.h>
47#include <QDir>
48#include <QStringList>
49#include <QMap>
50#include <QDir>
51#include <QFile>
52#include <QFileInfo>
53
54//#define CONFIGURE_DEBUG_EXECUTE
55//#define CONFIGURE_DEBUG_CP_DIR
56
57using namespace std;
58
59#ifdef Q_OS_WIN32
60#include <qt_windows.h>
61#endif
62
63#include <symbian/epocroot_p.h> // from tools/shared
64#include <windows/registry_p.h> // from tools/shared
65
66QT_BEGIN_NAMESPACE
67
68struct CompilerInfo{
69 Compiler compiler;
70 const char *compilerStr;
71 const char *regKey;
72 const char *executable;
73} compiler_info[] = {
74 // The compilers here are sorted in a reversed-preferred order
75 {CC_BORLAND, "Borland C++", 0, "bcc32.exe"},
76 {CC_MINGW, "MinGW (Minimalist GNU for Windows)", 0, "g++.exe"},
77 {CC_INTEL, "Intel(R) C++ Compiler for 32-bit applications", 0, "icl.exe"}, // xilink.exe, xilink5.exe, xilink6.exe, xilib.exe
78 {CC_MSVC6, "Microsoft (R) 32-bit C/C++ Optimizing Compiler (6.x)", "Software\\Microsoft\\VisualStudio\\6.0\\Setup\\Microsoft Visual C++\\ProductDir", "cl.exe"}, // link.exe, lib.exe
79 {CC_NET2002, "Microsoft (R) 32-bit C/C++ Optimizing Compiler.NET 2002 (7.0)", "Software\\Microsoft\\VisualStudio\\7.0\\Setup\\VC\\ProductDir", "cl.exe"}, // link.exe, lib.exe
80 {CC_NET2003, "Microsoft (R) 32-bit C/C++ Optimizing Compiler.NET 2003 (7.1)", "Software\\Microsoft\\VisualStudio\\7.1\\Setup\\VC\\ProductDir", "cl.exe"}, // link.exe, lib.exe
81 {CC_NET2005, "Microsoft (R) 32-bit C/C++ Optimizing Compiler.NET 2005 (8.0)", "Software\\Microsoft\\VisualStudio\\SxS\\VC7\\8.0", "cl.exe"}, // link.exe, lib.exe
82 {CC_NET2008, "Microsoft (R) 32-bit C/C++ Optimizing Compiler.NET 2008 (9.0)", "Software\\Microsoft\\VisualStudio\\SxS\\VC7\\9.0", "cl.exe"}, // link.exe, lib.exe
83 {CC_NET2010, "Microsoft (R) 32-bit C/C++ Optimizing Compiler.NET 2010 (10.0)", "Software\\Microsoft\\VisualStudio\\SxS\\VC7\\10.0", "cl.exe"}, // link.exe, lib.exe
84 {CC_UNKNOWN, "Unknown", 0, 0},
85};
86
87
88// Initialize static variables
89Compiler Environment::detectedCompiler = CC_UNKNOWN;
90
91/*!
92 Returns the pointer to the CompilerInfo for a \a compiler.
93*/
94CompilerInfo *Environment::compilerInfo(Compiler compiler)
95{
96 int i = 0;
97 while(compiler_info[i].compiler != compiler && compiler_info[i].compiler != CC_UNKNOWN)
98 ++i;
99 return &(compiler_info[i]);
100}
101
102/*!
103 Returns the qmakespec for the compiler detected on the system.
104*/
105QString Environment::detectQMakeSpec()
106{
107 QString spec;
108 switch (detectCompiler()) {
109 case CC_NET2010:
110 spec = "win32-msvc2010";
111 break;
112 case CC_NET2008:
113 spec = "win32-msvc2008";
114 break;
115 case CC_NET2005:
116 spec = "win32-msvc2005";
117 break;
118 case CC_NET2003:
119 spec = "win32-msvc2003";
120 break;
121 case CC_NET2002:
122 spec = "win32-msvc2002";
123 break;
124 case CC_MSVC4:
125 case CC_MSVC5:
126 case CC_MSVC6:
127 spec = "win32-msvc";
128 break;
129 case CC_INTEL:
130 spec = "win32-icc";
131 break;
132 case CC_MINGW:
133 spec = "win32-g++";
134 break;
135 case CC_BORLAND:
136 spec = "win32-borland";
137 break;
138 default:
139 break;
140 }
141
142 return spec;
143}
144
145/*!
146 Returns the enum of the compiler which was detected on the system.
147 The compilers are detected in the order as entered into the
148 compiler_info list.
149
150 If more than one compiler is found, CC_UNKNOWN is returned.
151*/
152Compiler Environment::detectCompiler()
153{
154#ifndef Q_OS_WIN32
155 return MSVC6; // Always generate MSVC 6.0 versions on other platforms
156#else
157 if(detectedCompiler != CC_UNKNOWN)
158 return detectedCompiler;
159
160 int installed = 0;
161
162 // Check for compilers in registry first, to see which version is in PATH
163 QString paths = qgetenv("PATH");
164 QStringList pathlist = paths.toLower().split(";");
165 for(int i = 0; compiler_info[i].compiler; ++i) {
166 QString productPath = qt_readRegistryKey(HKEY_LOCAL_MACHINE, compiler_info[i].regKey).toLower();
167 if (productPath.length()) {
168 QStringList::iterator it;
169 for(it = pathlist.begin(); it != pathlist.end(); ++it) {
170 if((*it).contains(productPath)) {
171 ++installed;
172 detectedCompiler = compiler_info[i].compiler;
173 break;
174 }
175 }
176 }
177 }
178
179 // Now just go looking for the executables, and accept any executable as the lowest version
180 if (!installed) {
181 for(int i = 0; compiler_info[i].compiler; ++i) {
182 QString executable = QString(compiler_info[i].executable).toLower();
183 if (executable.length() && Environment::detectExecutable(executable)) {
184 ++installed;
185 detectedCompiler = compiler_info[i].compiler;
186 break;
187 }
188 }
189 }
190
191 if (installed > 1) {
192 cout << "Found more than one known compiler! Using \"" << compilerInfo(detectedCompiler)->compilerStr << "\"" << endl;
193 detectedCompiler = CC_UNKNOWN;
194 }
195 return detectedCompiler;
196#endif
197};
198
199/*!
200 Returns true if the \a executable could be loaded, else false.
201 This means that the executable either is in the current directory
202 or in the PATH.
203*/
204bool Environment::detectExecutable(const QString &executable)
205{
206 PROCESS_INFORMATION procInfo;
207 memset(&procInfo, 0, sizeof(procInfo));
208
209 STARTUPINFO startInfo;
210 memset(&startInfo, 0, sizeof(startInfo));
211 startInfo.cb = sizeof(startInfo);
212
213 bool couldExecute = CreateProcess(0, (wchar_t*)executable.utf16(),
214 0, 0, false,
215 CREATE_NO_WINDOW | CREATE_SUSPENDED,
216 0, 0, &startInfo, &procInfo);
217
218 if (couldExecute) {
219 CloseHandle(procInfo.hThread);
220 TerminateProcess(procInfo.hProcess, 0);
221 CloseHandle(procInfo.hProcess);
222 }
223 return couldExecute;
224}
225
226/*!
227 Creates a commandling from \a program and it \a arguments,
228 escaping characters that needs it.
229*/
230static QString qt_create_commandline(const QString &program, const QStringList &arguments)
231{
232 QString programName = program;
233 if (!programName.startsWith("\"") && !programName.endsWith("\"") && programName.contains(" "))
234 programName = "\"" + programName + "\"";
235 programName.replace("/", "\\");
236
237 QString args;
238 // add the prgram as the first arrg ... it works better
239 args = programName + " ";
240 for (int i=0; i<arguments.size(); ++i) {
241 QString tmp = arguments.at(i);
242 // in the case of \" already being in the string the \ must also be escaped
243 tmp.replace( "\\\"", "\\\\\"" );
244 // escape a single " because the arguments will be parsed
245 tmp.replace( "\"", "\\\"" );
246 if (tmp.isEmpty() || tmp.contains(' ') || tmp.contains('\t')) {
247 // The argument must not end with a \ since this would be interpreted
248 // as escaping the quote -- rather put the \ behind the quote: e.g.
249 // rather use "foo"\ than "foo\"
250 QString endQuote("\"");
251 int i = tmp.length();
252 while (i>0 && tmp.at(i-1) == '\\') {
253 --i;
254 endQuote += "\\";
255 }
256 args += QString(" \"") + tmp.left(i) + endQuote;
257 } else {
258 args += ' ' + tmp;
259 }
260 }
261 return args;
262}
263
264/*!
265 Creates a QByteArray of the \a environment.
266*/
267static QByteArray qt_create_environment(const QStringList &environment)
268{
269 QByteArray envlist;
270 if (environment.isEmpty())
271 return envlist;
272
273 int pos = 0;
274 // add PATH if necessary (for DLL loading)
275 QByteArray path = qgetenv("PATH");
276 if (environment.filter(QRegExp("^PATH=",Qt::CaseInsensitive)).isEmpty() && !path.isNull()) {
277 QString tmp = QString(QLatin1String("PATH=%1")).arg(QString::fromLocal8Bit(path));
278 uint tmpSize = sizeof(wchar_t) * (tmp.length() + 1);
279 envlist.resize(envlist.size() + tmpSize);
280 memcpy(envlist.data() + pos, tmp.utf16(), tmpSize);
281 pos += tmpSize;
282 }
283 // add the user environment
284 foreach (const QString &tmp, environment) {
285 uint tmpSize = sizeof(wchar_t) * (tmp.length() + 1);
286 envlist.resize(envlist.size() + tmpSize);
287 memcpy(envlist.data() + pos, tmp.utf16(), tmpSize);
288 pos += tmpSize;
289 }
290 // add the 2 terminating 0 (actually 4, just to be on the safe side)
291 envlist.resize(envlist.size() + 4);
292 envlist[pos++] = 0;
293 envlist[pos++] = 0;
294 envlist[pos++] = 0;
295 envlist[pos++] = 0;
296
297 return envlist;
298}
299
300/*!
301 Executes the command described in \a arguments, in the
302 environment inherited from the parent process, with the
303 \a additionalEnv settings applied.
304 \a removeEnv removes the specified environment variables from
305 the environment of the executed process.
306
307 Returns the exit value of the process, or -1 if the command could
308 not be executed.
309
310 This function uses _(w)spawnvpe to spawn a process by searching
311 through the PATH environment variable.
312*/
313int Environment::execute(QStringList arguments, const QStringList &additionalEnv, const QStringList &removeEnv)
314{
315#ifdef CONFIGURE_DEBUG_EXECUTE
316 qDebug() << "About to Execute: " << arguments;
317 qDebug() << " " << QDir::currentPath();
318 qDebug() << " " << additionalEnv;
319 qDebug() << " " << removeEnv;
320#endif
321 // Create the full environment from the current environment and
322 // the additionalEnv strings, then remove all variables defined
323 // in removeEnv
324 QMap<QString, QString> fullEnvMap;
325 LPWSTR envStrings = GetEnvironmentStrings();
326 if (envStrings) {
327 int strLen = 0;
328 for (LPWSTR envString = envStrings; *(envString); envString += strLen + 1) {
329 strLen = wcslen(envString);
330 QString str = QString((const QChar*)envString, strLen);
331 if (!str.startsWith("=")) { // These are added by the system
332 int sepIndex = str.indexOf('=');
333 fullEnvMap.insert(str.left(sepIndex).toUpper(), str.mid(sepIndex +1));
334 }
335 }
336 }
337 FreeEnvironmentStrings(envStrings);
338
339 // Add additionalEnv variables
340 for (int i = 0; i < additionalEnv.count(); ++i) {
341 const QString &str = additionalEnv.at(i);
342 int sepIndex = str.indexOf('=');
343 fullEnvMap.insert(str.left(sepIndex).toUpper(), str.mid(sepIndex +1));
344 }
345
346 // Remove removeEnv variables
347 for (int j = 0; j < removeEnv.count(); ++j)
348 fullEnvMap.remove(removeEnv.at(j).toUpper());
349
350 // Add all variables to a QStringList
351 QStringList fullEnv;
352 QMapIterator<QString, QString> it(fullEnvMap);
353 while (it.hasNext()) {
354 it.next();
355 fullEnv += QString(it.key() + "=" + it.value());
356 }
357
358 // ----------------------------
359 QString program = arguments.takeAt(0);
360 QString args = qt_create_commandline(program, arguments);
361 QByteArray envlist = qt_create_environment(fullEnv);
362
363 DWORD exitCode = DWORD(-1);
364 PROCESS_INFORMATION procInfo;
365 memset(&procInfo, 0, sizeof(procInfo));
366
367 STARTUPINFO startInfo;
368 memset(&startInfo, 0, sizeof(startInfo));
369 startInfo.cb = sizeof(startInfo);
370
371 bool couldExecute = CreateProcess(0, (wchar_t*)args.utf16(),
372 0, 0, true, CREATE_UNICODE_ENVIRONMENT,
373 envlist.isEmpty() ? 0 : envlist.data(),
374 0, &startInfo, &procInfo);
375
376 if (couldExecute) {
377 WaitForSingleObject(procInfo.hProcess, INFINITE);
378 GetExitCodeProcess(procInfo.hProcess, &exitCode);
379 CloseHandle(procInfo.hThread);
380 CloseHandle(procInfo.hProcess);
381 }
382
383
384 if (exitCode == DWORD(-1)) {
385 switch(GetLastError()) {
386 case E2BIG:
387 cerr << "execute: Argument list exceeds 1024 bytes" << endl;
388 foreach (const QString &arg, arguments)
389 cerr << " (" << arg.toLocal8Bit().constData() << ")" << endl;
390 break;
391 case ENOENT:
392 cerr << "execute: File or path is not found (" << program.toLocal8Bit().constData() << ")" << endl;
393 break;
394 case ENOEXEC:
395 cerr << "execute: Specified file is not executable or has invalid executable-file format (" << program.toLocal8Bit().constData() << ")" << endl;
396 break;
397 case ENOMEM:
398 cerr << "execute: Not enough memory is available to execute new process." << endl;
399 break;
400 default:
401 cerr << "execute: Unknown error" << endl;
402 foreach (const QString &arg, arguments)
403 cerr << " (" << arg.toLocal8Bit().constData() << ")" << endl;
404 break;
405 }
406 }
407 return exitCode;
408}
409
410/*!
411 Copies the \a srcDir contents into \a destDir.
412
413 If \a includeSrcDir is not empty, any files with 'h', 'prf', or 'conf' suffixes
414 will not be copied over from \a srcDir. Instead a new file will be created
415 in \a destDir with the same name and that file will include a file with the
416 same name from the \a includeSrcDir using relative path and appropriate
417 syntax for the file type.
418
419 Returns true if copying was successful.
420*/
421bool Environment::cpdir(const QString &srcDir,
422 const QString &destDir,
423 const QString &includeSrcDir)
424{
425 QString cleanSrcName = QDir::cleanPath(srcDir);
426 QString cleanDstName = QDir::cleanPath(destDir);
427 QString cleanIncludeName = QDir::cleanPath(includeSrcDir);
428
429#ifdef CONFIGURE_DEBUG_CP_DIR
430 qDebug() << "Attempt to cpdir " << cleanSrcName << "->" << cleanDstName;
431#endif
432 if(!QFile::exists(cleanDstName) && !QDir().mkpath(cleanDstName)) {
433 qDebug() << "cpdir: Failure to create " << cleanDstName;
434 return false;
435 }
436
437 bool result = true;
438 QDir dir = QDir(cleanSrcName);
439 QDir destinationDir = QDir(cleanDstName);
440 QFileInfoList allEntries = dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
441 for (int i = 0; result && (i < allEntries.count()); ++i) {
442 QFileInfo entry = allEntries.at(i);
443 bool intermediate = true;
444 if (entry.isDir()) {
445 QString newIncSrcDir;
446 if (!includeSrcDir.isEmpty())
447 newIncSrcDir = QString("%1/%2").arg(cleanIncludeName).arg(entry.fileName());
448
449 intermediate = cpdir(QString("%1/%2").arg(cleanSrcName).arg(entry.fileName()),
450 QString("%1/%2").arg(cleanDstName).arg(entry.fileName()),
451 newIncSrcDir);
452 } else {
453 QString destFile = QString("%1/%2").arg(cleanDstName).arg(entry.fileName());
454#ifdef CONFIGURE_DEBUG_CP_DIR
455 qDebug() << "About to cp (file)" << entry.absoluteFilePath() << "->" << destFile;
456#endif
457 QFile::remove(destFile);
458 QString suffix = entry.suffix();
459 if (!includeSrcDir.isEmpty() && (suffix == "prf" || suffix == "conf" || suffix == "h")) {
460 QString relativeIncludeFilePath = QString("%1/%2").arg(cleanIncludeName).arg(entry.fileName());
461 relativeIncludeFilePath = destinationDir.relativeFilePath(relativeIncludeFilePath);
462#ifdef CONFIGURE_DEBUG_CP_DIR
463 qDebug() << "...instead generate relative include to" << relativeIncludeFilePath;
464#endif
465 QFile currentFile(destFile);
466 if (currentFile.open(QFile::WriteOnly | QFile::Text)) {
467 QTextStream fileStream;
468 fileStream.setDevice(&currentFile);
469
470 if (suffix == "prf" || suffix == "conf") {
471 if (entry.fileName() == "qmake.conf") {
472 // While QMAKESPEC_ORIGINAL being relative or absolute doesn't matter for the
473 // primary use of this variable by qmake to identify the original mkspec, the
474 // variable is also used for few special cases where the absolute path is required.
475 // Conversely, the include of the original qmake.conf must be done using relative path,
476 // as some Qt binary deployments are done in a manner that doesn't allow for patching
477 // the paths at the installation time.
478 fileStream << "QMAKESPEC_ORIGINAL=" << cleanSrcName << endl << endl;
479 }
480 fileStream << "include(" << relativeIncludeFilePath << ")" << endl << endl;
481 } else if (suffix == "h") {
482 fileStream << "#include \"" << relativeIncludeFilePath << "\"" << endl << endl;
483 }
484
485 fileStream.flush();
486 currentFile.close();
487 }
488 } else {
489 intermediate = QFile::copy(entry.absoluteFilePath(), destFile);
490 SetFileAttributes((wchar_t*)destFile.utf16(), FILE_ATTRIBUTE_NORMAL);
491 }
492 }
493 if(!intermediate) {
494 qDebug() << "cpdir: Failure for " << entry.fileName() << entry.isDir();
495 result = false;
496 }
497 }
498 return result;
499}
500
501bool Environment::rmdir(const QString &name)
502{
503 bool result = true;
504 QString cleanName = QDir::cleanPath(name);
505
506 QDir dir = QDir(cleanName);
507 QFileInfoList allEntries = dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
508 for (int i = 0; result && (i < allEntries.count()); ++i) {
509 QFileInfo entry = allEntries.at(i);
510 if (entry.isDir()) {
511 result &= rmdir(entry.absoluteFilePath());
512 } else {
513 result &= QFile::remove(entry.absoluteFilePath());
514 }
515 }
516 result &= dir.rmdir(cleanName);
517 return result;
518}
519
520QString Environment::symbianEpocRoot()
521{
522 // Call function defined in tools/shared/symbian/epocroot_p.h
523 return ::qt_epocRoot();
524}
525
526QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.