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

Last change on this file since 564 was 561, checked in by Dmitry A. Kuminov, 15 years ago

trunk: Merged in qt 4.6.1 sources.

File size: 19.6 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 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
64QT_BEGIN_NAMESPACE
65
66struct CompilerInfo{
67 Compiler compiler;
68 const char *compilerStr;
69 const char *regKey;
70 const char *executable;
71} compiler_info[] = {
72 // The compilers here are sorted in a reversed-preferred order
73 {CC_BORLAND, "Borland C++", 0, "bcc32.exe"},
74 {CC_MINGW, "MinGW (Minimalist GNU for Windows)", 0, "mingw32-gcc.exe"},
75 {CC_INTEL, "Intel(R) C++ Compiler for 32-bit applications", 0, "icl.exe"}, // xilink.exe, xilink5.exe, xilink6.exe, xilib.exe
76 {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
77 {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
78 {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
79 {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
80 {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
81 {CC_UNKNOWN, "Unknown", 0, 0},
82};
83
84
85// Initialize static variables
86Compiler Environment::detectedCompiler = CC_UNKNOWN;
87
88/*!
89 Returns the pointer to the CompilerInfo for a \a compiler.
90*/
91CompilerInfo *Environment::compilerInfo(Compiler compiler)
92{
93 int i = 0;
94 while(compiler_info[i].compiler != compiler && compiler_info[i].compiler != CC_UNKNOWN)
95 ++i;
96 return &(compiler_info[i]);
97}
98
99/*!
100 Returns the path part of a registry key.
101 Ei.
102 For a key
103 "Software\\Microsoft\\VisualStudio\\8.0\\Setup\\VC\\ProductDir"
104 it returns
105 "Software\\Microsoft\\VisualStudio\\8.0\\Setup\\VC\\"
106*/
107QString Environment::keyPath(const QString &rKey)
108{
109 int idx = rKey.lastIndexOf(QLatin1Char('\\'));
110 if (idx == -1)
111 return QString();
112 return rKey.left(idx + 1);
113}
114
115/*!
116 Returns the name part of a registry key.
117 Ei.
118 For a key
119 "Software\\Microsoft\\VisualStudio\\8.0\\Setup\\VC\\ProductDir"
120 it returns
121 "ProductDir"
122*/
123QString Environment::keyName(const QString &rKey)
124{
125 int idx = rKey.lastIndexOf(QLatin1Char('\\'));
126 if (idx == -1)
127 return rKey;
128
129 QString res(rKey.mid(idx + 1));
130 if (res == "Default" || res == ".")
131 res = "";
132 return res;
133}
134
135/*!
136 Returns a registry keys value in string form.
137 If the registry key does not exist, or cannot be accessed, a
138 QString() is returned.
139*/
140QString Environment::readRegistryKey(HKEY parentHandle, const QString &rSubkey)
141{
142#ifndef Q_OS_WIN32
143 return QString();
144#else
145 QString rSubkeyName = keyName(rSubkey);
146 QString rSubkeyPath = keyPath(rSubkey);
147
148 HKEY handle = 0;
149 LONG res = RegOpenKeyEx(parentHandle, (wchar_t*)rSubkeyPath.utf16(), 0, KEY_READ, &handle);
150 if (res != ERROR_SUCCESS)
151 return QString();
152
153 // get the size and type of the value
154 DWORD dataType;
155 DWORD dataSize;
156 res = RegQueryValueEx(handle, (wchar_t*)rSubkeyName.utf16(), 0, &dataType, 0, &dataSize);
157 if (res != ERROR_SUCCESS) {
158 RegCloseKey(handle);
159 return QString();
160 }
161
162 // get the value
163 QByteArray data(dataSize, 0);
164 res = RegQueryValueEx(handle, (wchar_t*)rSubkeyName.utf16(), 0, 0,
165 reinterpret_cast<unsigned char*>(data.data()), &dataSize);
166 if (res != ERROR_SUCCESS) {
167 RegCloseKey(handle);
168 return QString();
169 }
170
171 QString result;
172 switch (dataType) {
173 case REG_EXPAND_SZ:
174 case REG_SZ: {
175 result = QString::fromWCharArray(((const wchar_t *)data.constData()));
176 break;
177 }
178
179 case REG_MULTI_SZ: {
180 QStringList l;
181 int i = 0;
182 for (;;) {
183 QString s = QString::fromWCharArray((const wchar_t *)data.constData() + i);
184 i += s.length() + 1;
185
186 if (s.isEmpty())
187 break;
188 l.append(s);
189 }
190 result = l.join(", ");
191 break;
192 }
193
194 case REG_NONE:
195 case REG_BINARY: {
196 result = QString::fromWCharArray((const wchar_t *)data.constData(), data.size() / 2);
197 break;
198 }
199
200 case REG_DWORD_BIG_ENDIAN:
201 case REG_DWORD: {
202 Q_ASSERT(data.size() == sizeof(int));
203 int i;
204 memcpy((char*)&i, data.constData(), sizeof(int));
205 result = QString::number(i);
206 break;
207 }
208
209 default:
210 qWarning("QSettings: unknown data %d type in windows registry", dataType);
211 break;
212 }
213
214 RegCloseKey(handle);
215 return result;
216#endif
217}
218
219/*!
220 Returns the qmakespec for the compiler detected on the system.
221*/
222QString Environment::detectQMakeSpec()
223{
224 QString spec;
225 switch (detectCompiler()) {
226 case CC_NET2008:
227 spec = "win32-msvc2008";
228 break;
229 case CC_NET2005:
230 spec = "win32-msvc2005";
231 break;
232 case CC_NET2003:
233 spec = "win32-msvc2003";
234 break;
235 case CC_NET2002:
236 spec = "win32-msvc2002";
237 break;
238 case CC_MSVC4:
239 case CC_MSVC5:
240 case CC_MSVC6:
241 spec = "win32-msvc";
242 break;
243 case CC_INTEL:
244 spec = "win32-icc";
245 break;
246 case CC_MINGW:
247 spec = "win32-g++";
248 break;
249 case CC_BORLAND:
250 spec = "win32-borland";
251 break;
252 default:
253 break;
254 }
255
256 return spec;
257}
258
259/*!
260 Returns the enum of the compiler which was detected on the system.
261 The compilers are detected in the order as entered into the
262 compiler_info list.
263
264 If more than one compiler is found, CC_UNKNOWN is returned.
265*/
266Compiler Environment::detectCompiler()
267{
268#ifndef Q_OS_WIN32
269 return MSVC6; // Always generate MSVC 6.0 versions on other platforms
270#else
271 if(detectedCompiler != CC_UNKNOWN)
272 return detectedCompiler;
273
274 int installed = 0;
275
276 // Check for compilers in registry first, to see which version is in PATH
277 QString paths = qgetenv("PATH");
278 QStringList pathlist = paths.toLower().split(";");
279 for(int i = 0; compiler_info[i].compiler; ++i) {
280 QString productPath = readRegistryKey(HKEY_LOCAL_MACHINE, compiler_info[i].regKey).toLower();
281 if (productPath.length()) {
282 QStringList::iterator it;
283 for(it = pathlist.begin(); it != pathlist.end(); ++it) {
284 if((*it).contains(productPath)) {
285 ++installed;
286 detectedCompiler = compiler_info[i].compiler;
287 break;
288 }
289 }
290 }
291 }
292
293 // Now just go looking for the executables, and accept any executable as the lowest version
294 if (!installed) {
295 for(int i = 0; compiler_info[i].compiler; ++i) {
296 QString executable = QString(compiler_info[i].executable).toLower();
297 if (executable.length() && Environment::detectExecutable(executable)) {
298 ++installed;
299 detectedCompiler = compiler_info[i].compiler;
300 break;
301 }
302 }
303 }
304
305 if (installed > 1) {
306 cout << "Found more than one known compiler! Using \"" << compilerInfo(detectedCompiler)->compilerStr << "\"" << endl;
307 detectedCompiler = CC_UNKNOWN;
308 }
309 return detectedCompiler;
310#endif
311};
312
313/*!
314 Returns true if the \a executable could be loaded, else false.
315 This means that the executable either is in the current directory
316 or in the PATH.
317*/
318bool Environment::detectExecutable(const QString &executable)
319{
320 PROCESS_INFORMATION procInfo;
321 memset(&procInfo, 0, sizeof(procInfo));
322
323 STARTUPINFO startInfo;
324 memset(&startInfo, 0, sizeof(startInfo));
325 startInfo.cb = sizeof(startInfo);
326
327 bool couldExecute = CreateProcess(0, (wchar_t*)executable.utf16(),
328 0, 0, false,
329 CREATE_NO_WINDOW | CREATE_SUSPENDED,
330 0, 0, &startInfo, &procInfo);
331
332 if (couldExecute) {
333 CloseHandle(procInfo.hThread);
334 TerminateProcess(procInfo.hProcess, 0);
335 CloseHandle(procInfo.hProcess);
336 }
337 return couldExecute;
338}
339
340/*!
341 Creates a commandling from \a program and it \a arguments,
342 escaping characters that needs it.
343*/
344static QString qt_create_commandline(const QString &program, const QStringList &arguments)
345{
346 QString programName = program;
347 if (!programName.startsWith("\"") && !programName.endsWith("\"") && programName.contains(" "))
348 programName = "\"" + programName + "\"";
349 programName.replace("/", "\\");
350
351 QString args;
352 // add the prgram as the first arrg ... it works better
353 args = programName + " ";
354 for (int i=0; i<arguments.size(); ++i) {
355 QString tmp = arguments.at(i);
356 // in the case of \" already being in the string the \ must also be escaped
357 tmp.replace( "\\\"", "\\\\\"" );
358 // escape a single " because the arguments will be parsed
359 tmp.replace( "\"", "\\\"" );
360 if (tmp.isEmpty() || tmp.contains(' ') || tmp.contains('\t')) {
361 // The argument must not end with a \ since this would be interpreted
362 // as escaping the quote -- rather put the \ behind the quote: e.g.
363 // rather use "foo"\ than "foo\"
364 QString endQuote("\"");
365 int i = tmp.length();
366 while (i>0 && tmp.at(i-1) == '\\') {
367 --i;
368 endQuote += "\\";
369 }
370 args += QString(" \"") + tmp.left(i) + endQuote;
371 } else {
372 args += ' ' + tmp;
373 }
374 }
375 return args;
376}
377
378/*!
379 Creates a QByteArray of the \a environment.
380*/
381static QByteArray qt_create_environment(const QStringList &environment)
382{
383 QByteArray envlist;
384 if (environment.isEmpty())
385 return envlist;
386
387 int pos = 0;
388 // add PATH if necessary (for DLL loading)
389 QByteArray path = qgetenv("PATH");
390 if (environment.filter(QRegExp("^PATH=",Qt::CaseInsensitive)).isEmpty() && !path.isNull()) {
391 QString tmp = QString(QLatin1String("PATH=%1")).arg(QString::fromLocal8Bit(path));
392 uint tmpSize = sizeof(wchar_t) * (tmp.length() + 1);
393 envlist.resize(envlist.size() + tmpSize);
394 memcpy(envlist.data() + pos, tmp.utf16(), tmpSize);
395 pos += tmpSize;
396 }
397 // add the user environment
398 for (QStringList::ConstIterator it = environment.begin(); it != environment.end(); it++ ) {
399 QString tmp = *it;
400 uint tmpSize = sizeof(wchar_t) * (tmp.length() + 1);
401 envlist.resize(envlist.size() + tmpSize);
402 memcpy(envlist.data() + pos, tmp.utf16(), tmpSize);
403 pos += tmpSize;
404 }
405 // add the 2 terminating 0 (actually 4, just to be on the safe side)
406 envlist.resize(envlist.size() + 4);
407 envlist[pos++] = 0;
408 envlist[pos++] = 0;
409 envlist[pos++] = 0;
410 envlist[pos++] = 0;
411
412 return envlist;
413}
414
415/*!
416 Executes the command described in \a arguments, in the
417 environment inherited from the parent process, with the
418 \a additionalEnv settings applied.
419 \a removeEnv removes the specified environment variables from
420 the environment of the executed process.
421
422 Returns the exit value of the process, or -1 if the command could
423 not be executed.
424
425 This function uses _(w)spawnvpe to spawn a process by searching
426 through the PATH environment variable.
427*/
428int Environment::execute(QStringList arguments, const QStringList &additionalEnv, const QStringList &removeEnv)
429{
430#ifdef CONFIGURE_DEBUG_EXECUTE
431 qDebug() << "About to Execute: " << arguments;
432 qDebug() << " " << QDir::currentPath();
433 qDebug() << " " << additionalEnv;
434 qDebug() << " " << removeEnv;
435#endif
436 // Create the full environment from the current environment and
437 // the additionalEnv strings, then remove all variables defined
438 // in removeEnv
439 QMap<QString, QString> fullEnvMap;
440 LPWSTR envStrings = GetEnvironmentStrings();
441 if (envStrings) {
442 int strLen = 0;
443 for (LPWSTR envString = envStrings; *(envString); envString += strLen + 1) {
444 strLen = wcslen(envString);
445 QString str = QString((const QChar*)envString, strLen);
446 if (!str.startsWith("=")) { // These are added by the system
447 int sepIndex = str.indexOf('=');
448 fullEnvMap.insert(str.left(sepIndex).toUpper(), str.mid(sepIndex +1));
449 }
450 }
451 }
452 FreeEnvironmentStrings(envStrings);
453
454 // Add additionalEnv variables
455 for (int i = 0; i < additionalEnv.count(); ++i) {
456 const QString &str = additionalEnv.at(i);
457 int sepIndex = str.indexOf('=');
458 fullEnvMap.insert(str.left(sepIndex).toUpper(), str.mid(sepIndex +1));
459 }
460
461 // Remove removeEnv variables
462 for (int j = 0; j < removeEnv.count(); ++j)
463 fullEnvMap.remove(removeEnv.at(j).toUpper());
464
465 // Add all variables to a QStringList
466 QStringList fullEnv;
467 QMapIterator<QString, QString> it(fullEnvMap);
468 while (it.hasNext()) {
469 it.next();
470 fullEnv += QString(it.key() + "=" + it.value());
471 }
472
473 // ----------------------------
474 QString program = arguments.takeAt(0);
475 QString args = qt_create_commandline(program, arguments);
476 QByteArray envlist = qt_create_environment(fullEnv);
477
478 DWORD exitCode = -1;
479 PROCESS_INFORMATION procInfo;
480 memset(&procInfo, 0, sizeof(procInfo));
481
482 STARTUPINFO startInfo;
483 memset(&startInfo, 0, sizeof(startInfo));
484 startInfo.cb = sizeof(startInfo);
485
486 bool couldExecute = CreateProcess(0, (wchar_t*)args.utf16(),
487 0, 0, true, CREATE_UNICODE_ENVIRONMENT,
488 envlist.isEmpty() ? 0 : envlist.data(),
489 0, &startInfo, &procInfo);
490
491 if (couldExecute) {
492 WaitForSingleObject(procInfo.hProcess, INFINITE);
493 GetExitCodeProcess(procInfo.hProcess, &exitCode);
494 CloseHandle(procInfo.hThread);
495 CloseHandle(procInfo.hProcess);
496 }
497
498
499 if (exitCode == -1) {
500 switch(GetLastError()) {
501 case E2BIG:
502 cerr << "execute: Argument list exceeds 1024 bytes" << endl;
503 foreach(QString arg, arguments)
504 cerr << " (" << arg.toLocal8Bit().constData() << ")" << endl;
505 break;
506 case ENOENT:
507 cerr << "execute: File or path is not found (" << program.toLocal8Bit().constData() << ")" << endl;
508 break;
509 case ENOEXEC:
510 cerr << "execute: Specified file is not executable or has invalid executable-file format (" << program.toLocal8Bit().constData() << ")" << endl;
511 break;
512 case ENOMEM:
513 cerr << "execute: Not enough memory is available to execute new process." << endl;
514 break;
515 default:
516 cerr << "execute: Unknown error" << endl;
517 foreach(QString arg, arguments)
518 cerr << " (" << arg.toLocal8Bit().constData() << ")" << endl;
519 break;
520 }
521 }
522 return exitCode;
523}
524
525bool Environment::cpdir(const QString &srcDir, const QString &destDir)
526{
527 QString cleanSrcName = QDir::cleanPath(srcDir);
528 QString cleanDstName = QDir::cleanPath(destDir);
529#ifdef CONFIGURE_DEBUG_CP_DIR
530 qDebug() << "Attempt to cpdir " << cleanSrcName << "->" << cleanDstName;
531#endif
532 if(!QFile::exists(cleanDstName) && !QDir().mkpath(cleanDstName)) {
533 qDebug() << "cpdir: Failure to create " << cleanDstName;
534 return false;
535 }
536
537 bool result = true;
538 QDir dir = QDir(cleanSrcName);
539 QFileInfoList allEntries = dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
540 for (int i = 0; result && (i < allEntries.count()); ++i) {
541 QFileInfo entry = allEntries.at(i);
542 bool intermediate = true;
543 if (entry.isDir()) {
544 intermediate = cpdir(QString("%1/%2").arg(cleanSrcName).arg(entry.fileName()),
545 QString("%1/%2").arg(cleanDstName).arg(entry.fileName()));
546 } else {
547 QString destFile = QString("%1/%2").arg(cleanDstName).arg(entry.fileName());
548#ifdef CONFIGURE_DEBUG_CP_DIR
549 qDebug() << "About to cp (file)" << entry.absoluteFilePath() << "->" << destFile;
550#endif
551 QFile::remove(destFile);
552 intermediate = QFile::copy(entry.absoluteFilePath(), destFile);
553 SetFileAttributes((wchar_t*)destFile.utf16(), FILE_ATTRIBUTE_NORMAL);
554 }
555 if(!intermediate) {
556 qDebug() << "cpdir: Failure for " << entry.fileName() << entry.isDir();
557 result = false;
558 }
559 }
560 return result;
561}
562
563bool Environment::rmdir(const QString &name)
564{
565 bool result = true;
566 QString cleanName = QDir::cleanPath(name);
567
568 QDir dir = QDir(cleanName);
569 QFileInfoList allEntries = dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
570 for (int i = 0; result && (i < allEntries.count()); ++i) {
571 QFileInfo entry = allEntries.at(i);
572 if (entry.isDir()) {
573 result &= rmdir(entry.absoluteFilePath());
574 } else {
575 result &= QFile::remove(entry.absoluteFilePath());
576 }
577 }
578 result &= dir.rmdir(cleanName);
579 return result;
580}
581
582QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.