/**************************************************************************** | |
** | |
** Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). | |
** All rights reserved. | |
** Contact: Nokia Corporation (qt-info@nokia.com) | |
** | |
** This file is part of the tools applications of the Qt Toolkit. | |
** | |
** $QT_BEGIN_LICENSE:LGPL$ | |
** GNU Lesser General Public License Usage | |
** This file may be used under the terms of the GNU Lesser General Public | |
** License version 2.1 as published by the Free Software Foundation and | |
** appearing in the file LICENSE.LGPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU Lesser | |
** General Public License version 2.1 requirements will be met: | |
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. | |
** | |
** In addition, as a special exception, Nokia gives you certain additional | |
** rights. These rights are described in the Nokia Qt LGPL Exception | |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. | |
** | |
** GNU General Public License Usage | |
** Alternatively, this file may be used under the terms of the GNU General | |
** Public License version 3.0 as published by the Free Software Foundation | |
** and appearing in the file LICENSE.GPL included in the packaging of this | |
** file. Please review the following information to ensure the GNU General | |
** Public License version 3.0 requirements will be met: | |
** http://www.gnu.org/copyleft/gpl.html. | |
** | |
** Other Usage | |
** Alternatively, this file may be used in accordance with the terms and | |
** conditions contained in a signed written agreement between you and Nokia. | |
** | |
** | |
** | |
** | |
** | |
** $QT_END_LICENSE$ | |
** | |
****************************************************************************/ | |
#include "environment.h" | |
#include <process.h> | |
#include <iostream> | |
#include <qdebug.h> | |
#include <QDir> | |
#include <QStringList> | |
#include <QMap> | |
#include <QDir> | |
#include <QFile> | |
#include <QFileInfo> | |
//#define CONFIGURE_DEBUG_EXECUTE | |
//#define CONFIGURE_DEBUG_CP_DIR | |
using namespace std; | |
#ifdef Q_OS_WIN32 | |
#include <qt_windows.h> | |
#endif | |
#include <symbian/epocroot_p.h> // from tools/shared | |
#include <windows/registry_p.h> // from tools/shared | |
QT_BEGIN_NAMESPACE | |
struct CompilerInfo{ | |
Compiler compiler; | |
const char *compilerStr; | |
const char *regKey; | |
const char *executable; | |
} compiler_info[] = { | |
// The compilers here are sorted in a reversed-preferred order | |
{CC_BORLAND, "Borland C++", 0, "bcc32.exe"}, | |
{CC_MINGW, "MinGW (Minimalist GNU for Windows)", 0, "g++.exe"}, | |
{CC_INTEL, "Intel(R) C++ Compiler for 32-bit applications", 0, "icl.exe"}, // xilink.exe, xilink5.exe, xilink6.exe, xilib.exe | |
{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 | |
{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 | |
{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 | |
{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 | |
{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 | |
{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 | |
{CC_UNKNOWN, "Unknown", 0, 0}, | |
}; | |
// Initialize static variables | |
Compiler Environment::detectedCompiler = CC_UNKNOWN; | |
/*! | |
Returns the pointer to the CompilerInfo for a \a compiler. | |
*/ | |
CompilerInfo *Environment::compilerInfo(Compiler compiler) | |
{ | |
int i = 0; | |
while(compiler_info[i].compiler != compiler && compiler_info[i].compiler != CC_UNKNOWN) | |
++i; | |
return &(compiler_info[i]); | |
} | |
/*! | |
Returns the qmakespec for the compiler detected on the system. | |
*/ | |
QString Environment::detectQMakeSpec() | |
{ | |
QString spec; | |
switch (detectCompiler()) { | |
case CC_NET2010: | |
spec = "win32-msvc2010"; | |
break; | |
case CC_NET2008: | |
spec = "win32-msvc2008"; | |
break; | |
case CC_NET2005: | |
spec = "win32-msvc2005"; | |
break; | |
case CC_NET2003: | |
spec = "win32-msvc2003"; | |
break; | |
case CC_NET2002: | |
spec = "win32-msvc2002"; | |
break; | |
case CC_MSVC4: | |
case CC_MSVC5: | |
case CC_MSVC6: | |
spec = "win32-msvc"; | |
break; | |
case CC_INTEL: | |
spec = "win32-icc"; | |
break; | |
case CC_MINGW: | |
spec = "win32-g++"; | |
break; | |
case CC_BORLAND: | |
spec = "win32-borland"; | |
break; | |
default: | |
break; | |
} | |
return spec; | |
} | |
/*! | |
Returns the enum of the compiler which was detected on the system. | |
The compilers are detected in the order as entered into the | |
compiler_info list. | |
If more than one compiler is found, CC_UNKNOWN is returned. | |
*/ | |
Compiler Environment::detectCompiler() | |
{ | |
#ifndef Q_OS_WIN32 | |
return MSVC6; // Always generate MSVC 6.0 versions on other platforms | |
#else | |
if(detectedCompiler != CC_UNKNOWN) | |
return detectedCompiler; | |
int installed = 0; | |
// Check for compilers in registry first, to see which version is in PATH | |
QString paths = qgetenv("PATH"); | |
QStringList pathlist = paths.toLower().split(";"); | |
for(int i = 0; compiler_info[i].compiler; ++i) { | |
QString productPath = qt_readRegistryKey(HKEY_LOCAL_MACHINE, compiler_info[i].regKey).toLower(); | |
if (productPath.length()) { | |
QStringList::iterator it; | |
for(it = pathlist.begin(); it != pathlist.end(); ++it) { | |
if((*it).contains(productPath)) { | |
++installed; | |
detectedCompiler = compiler_info[i].compiler; | |
break; | |
} | |
} | |
} | |
} | |
// Now just go looking for the executables, and accept any executable as the lowest version | |
if (!installed) { | |
for(int i = 0; compiler_info[i].compiler; ++i) { | |
QString executable = QString(compiler_info[i].executable).toLower(); | |
if (executable.length() && Environment::detectExecutable(executable)) { | |
++installed; | |
detectedCompiler = compiler_info[i].compiler; | |
break; | |
} | |
} | |
} | |
if (installed > 1) { | |
cout << "Found more than one known compiler! Using \"" << compilerInfo(detectedCompiler)->compilerStr << "\"" << endl; | |
detectedCompiler = CC_UNKNOWN; | |
} | |
return detectedCompiler; | |
#endif | |
}; | |
/*! | |
Returns true if the \a executable could be loaded, else false. | |
This means that the executable either is in the current directory | |
or in the PATH. | |
*/ | |
bool Environment::detectExecutable(const QString &executable) | |
{ | |
PROCESS_INFORMATION procInfo; | |
memset(&procInfo, 0, sizeof(procInfo)); | |
STARTUPINFO startInfo; | |
memset(&startInfo, 0, sizeof(startInfo)); | |
startInfo.cb = sizeof(startInfo); | |
bool couldExecute = CreateProcess(0, (wchar_t*)executable.utf16(), | |
0, 0, false, | |
CREATE_NO_WINDOW | CREATE_SUSPENDED, | |
0, 0, &startInfo, &procInfo); | |
if (couldExecute) { | |
CloseHandle(procInfo.hThread); | |
TerminateProcess(procInfo.hProcess, 0); | |
CloseHandle(procInfo.hProcess); | |
} | |
return couldExecute; | |
} | |
/*! | |
Creates a commandling from \a program and it \a arguments, | |
escaping characters that needs it. | |
*/ | |
static QString qt_create_commandline(const QString &program, const QStringList &arguments) | |
{ | |
QString programName = program; | |
if (!programName.startsWith("\"") && !programName.endsWith("\"") && programName.contains(" ")) | |
programName = "\"" + programName + "\""; | |
programName.replace("/", "\\"); | |
QString args; | |
// add the prgram as the first arrg ... it works better | |
args = programName + " "; | |
for (int i=0; i<arguments.size(); ++i) { | |
QString tmp = arguments.at(i); | |
// in the case of \" already being in the string the \ must also be escaped | |
tmp.replace( "\\\"", "\\\\\"" ); | |
// escape a single " because the arguments will be parsed | |
tmp.replace( "\"", "\\\"" ); | |
if (tmp.isEmpty() || tmp.contains(' ') || tmp.contains('\t')) { | |
// The argument must not end with a \ since this would be interpreted | |
// as escaping the quote -- rather put the \ behind the quote: e.g. | |
// rather use "foo"\ than "foo\" | |
QString endQuote("\""); | |
int i = tmp.length(); | |
while (i>0 && tmp.at(i-1) == '\\') { | |
--i; | |
endQuote += "\\"; | |
} | |
args += QString(" \"") + tmp.left(i) + endQuote; | |
} else { | |
args += ' ' + tmp; | |
} | |
} | |
return args; | |
} | |
/*! | |
Creates a QByteArray of the \a environment. | |
*/ | |
static QByteArray qt_create_environment(const QStringList &environment) | |
{ | |
QByteArray envlist; | |
if (environment.isEmpty()) | |
return envlist; | |
int pos = 0; | |
// add PATH if necessary (for DLL loading) | |
QByteArray path = qgetenv("PATH"); | |
if (environment.filter(QRegExp("^PATH=",Qt::CaseInsensitive)).isEmpty() && !path.isNull()) { | |
QString tmp = QString(QLatin1String("PATH=%1")).arg(QString::fromLocal8Bit(path)); | |
uint tmpSize = sizeof(wchar_t) * (tmp.length() + 1); | |
envlist.resize(envlist.size() + tmpSize); | |
memcpy(envlist.data() + pos, tmp.utf16(), tmpSize); | |
pos += tmpSize; | |
} | |
// add the user environment | |
foreach (const QString &tmp, environment) { | |
uint tmpSize = sizeof(wchar_t) * (tmp.length() + 1); | |
envlist.resize(envlist.size() + tmpSize); | |
memcpy(envlist.data() + pos, tmp.utf16(), tmpSize); | |
pos += tmpSize; | |
} | |
// add the 2 terminating 0 (actually 4, just to be on the safe side) | |
envlist.resize(envlist.size() + 4); | |
envlist[pos++] = 0; | |
envlist[pos++] = 0; | |
envlist[pos++] = 0; | |
envlist[pos++] = 0; | |
return envlist; | |
} | |
/*! | |
Executes the command described in \a arguments, in the | |
environment inherited from the parent process, with the | |
\a additionalEnv settings applied. | |
\a removeEnv removes the specified environment variables from | |
the environment of the executed process. | |
Returns the exit value of the process, or -1 if the command could | |
not be executed. | |
This function uses _(w)spawnvpe to spawn a process by searching | |
through the PATH environment variable. | |
*/ | |
int Environment::execute(QStringList arguments, const QStringList &additionalEnv, const QStringList &removeEnv) | |
{ | |
#ifdef CONFIGURE_DEBUG_EXECUTE | |
qDebug() << "About to Execute: " << arguments; | |
qDebug() << " " << QDir::currentPath(); | |
qDebug() << " " << additionalEnv; | |
qDebug() << " " << removeEnv; | |
#endif | |
// Create the full environment from the current environment and | |
// the additionalEnv strings, then remove all variables defined | |
// in removeEnv | |
QMap<QString, QString> fullEnvMap; | |
LPWSTR envStrings = GetEnvironmentStrings(); | |
if (envStrings) { | |
int strLen = 0; | |
for (LPWSTR envString = envStrings; *(envString); envString += strLen + 1) { | |
strLen = wcslen(envString); | |
QString str = QString((const QChar*)envString, strLen); | |
if (!str.startsWith("=")) { // These are added by the system | |
int sepIndex = str.indexOf('='); | |
fullEnvMap.insert(str.left(sepIndex).toUpper(), str.mid(sepIndex +1)); | |
} | |
} | |
} | |
FreeEnvironmentStrings(envStrings); | |
// Add additionalEnv variables | |
for (int i = 0; i < additionalEnv.count(); ++i) { | |
const QString &str = additionalEnv.at(i); | |
int sepIndex = str.indexOf('='); | |
fullEnvMap.insert(str.left(sepIndex).toUpper(), str.mid(sepIndex +1)); | |
} | |
// Remove removeEnv variables | |
for (int j = 0; j < removeEnv.count(); ++j) | |
fullEnvMap.remove(removeEnv.at(j).toUpper()); | |
// Add all variables to a QStringList | |
QStringList fullEnv; | |
QMapIterator<QString, QString> it(fullEnvMap); | |
while (it.hasNext()) { | |
it.next(); | |
fullEnv += QString(it.key() + "=" + it.value()); | |
} | |
// ---------------------------- | |
QString program = arguments.takeAt(0); | |
QString args = qt_create_commandline(program, arguments); | |
QByteArray envlist = qt_create_environment(fullEnv); | |
DWORD exitCode = DWORD(-1); | |
PROCESS_INFORMATION procInfo; | |
memset(&procInfo, 0, sizeof(procInfo)); | |
STARTUPINFO startInfo; | |
memset(&startInfo, 0, sizeof(startInfo)); | |
startInfo.cb = sizeof(startInfo); | |
bool couldExecute = CreateProcess(0, (wchar_t*)args.utf16(), | |
0, 0, true, CREATE_UNICODE_ENVIRONMENT, | |
envlist.isEmpty() ? 0 : envlist.data(), | |
0, &startInfo, &procInfo); | |
if (couldExecute) { | |
WaitForSingleObject(procInfo.hProcess, INFINITE); | |
GetExitCodeProcess(procInfo.hProcess, &exitCode); | |
CloseHandle(procInfo.hThread); | |
CloseHandle(procInfo.hProcess); | |
} | |
if (exitCode == DWORD(-1)) { | |
switch(GetLastError()) { | |
case E2BIG: | |
cerr << "execute: Argument list exceeds 1024 bytes" << endl; | |
foreach (const QString &arg, arguments) | |
cerr << " (" << arg.toLocal8Bit().constData() << ")" << endl; | |
break; | |
case ENOENT: | |
cerr << "execute: File or path is not found (" << program.toLocal8Bit().constData() << ")" << endl; | |
break; | |
case ENOEXEC: | |
cerr << "execute: Specified file is not executable or has invalid executable-file format (" << program.toLocal8Bit().constData() << ")" << endl; | |
break; | |
case ENOMEM: | |
cerr << "execute: Not enough memory is available to execute new process." << endl; | |
break; | |
default: | |
cerr << "execute: Unknown error" << endl; | |
foreach (const QString &arg, arguments) | |
cerr << " (" << arg.toLocal8Bit().constData() << ")" << endl; | |
break; | |
} | |
} | |
return exitCode; | |
} | |
/*! | |
Copies the \a srcDir contents into \a destDir. | |
If \a includeSrcDir is not empty, any files with 'h', 'prf', or 'conf' suffixes | |
will not be copied over from \a srcDir. Instead a new file will be created | |
in \a destDir with the same name and that file will include a file with the | |
same name from the \a includeSrcDir using relative path and appropriate | |
syntax for the file type. | |
Returns true if copying was successful. | |
*/ | |
bool Environment::cpdir(const QString &srcDir, | |
const QString &destDir, | |
const QString &includeSrcDir) | |
{ | |
QString cleanSrcName = QDir::cleanPath(srcDir); | |
QString cleanDstName = QDir::cleanPath(destDir); | |
QString cleanIncludeName = QDir::cleanPath(includeSrcDir); | |
#ifdef CONFIGURE_DEBUG_CP_DIR | |
qDebug() << "Attempt to cpdir " << cleanSrcName << "->" << cleanDstName; | |
#endif | |
if(!QFile::exists(cleanDstName) && !QDir().mkpath(cleanDstName)) { | |
qDebug() << "cpdir: Failure to create " << cleanDstName; | |
return false; | |
} | |
bool result = true; | |
QDir dir = QDir(cleanSrcName); | |
QDir destinationDir = QDir(cleanDstName); | |
QFileInfoList allEntries = dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); | |
for (int i = 0; result && (i < allEntries.count()); ++i) { | |
QFileInfo entry = allEntries.at(i); | |
bool intermediate = true; | |
if (entry.isDir()) { | |
QString newIncSrcDir; | |
if (!includeSrcDir.isEmpty()) | |
newIncSrcDir = QString("%1/%2").arg(cleanIncludeName).arg(entry.fileName()); | |
intermediate = cpdir(QString("%1/%2").arg(cleanSrcName).arg(entry.fileName()), | |
QString("%1/%2").arg(cleanDstName).arg(entry.fileName()), | |
newIncSrcDir); | |
} else { | |
QString destFile = QString("%1/%2").arg(cleanDstName).arg(entry.fileName()); | |
#ifdef CONFIGURE_DEBUG_CP_DIR | |
qDebug() << "About to cp (file)" << entry.absoluteFilePath() << "->" << destFile; | |
#endif | |
QFile::remove(destFile); | |
QString suffix = entry.suffix(); | |
if (!includeSrcDir.isEmpty() && (suffix == "prf" || suffix == "conf" || suffix == "h")) { | |
QString relativeIncludeFilePath = QString("%1/%2").arg(cleanIncludeName).arg(entry.fileName()); | |
relativeIncludeFilePath = destinationDir.relativeFilePath(relativeIncludeFilePath); | |
#ifdef CONFIGURE_DEBUG_CP_DIR | |
qDebug() << "...instead generate relative include to" << relativeIncludeFilePath; | |
#endif | |
QFile currentFile(destFile); | |
if (currentFile.open(QFile::WriteOnly | QFile::Text)) { | |
QTextStream fileStream; | |
fileStream.setDevice(¤tFile); | |
if (suffix == "prf" || suffix == "conf") { | |
if (entry.fileName() == "qmake.conf") { | |
// While QMAKESPEC_ORIGINAL being relative or absolute doesn't matter for the | |
// primary use of this variable by qmake to identify the original mkspec, the | |
// variable is also used for few special cases where the absolute path is required. | |
// Conversely, the include of the original qmake.conf must be done using relative path, | |
// as some Qt binary deployments are done in a manner that doesn't allow for patching | |
// the paths at the installation time. | |
fileStream << "QMAKESPEC_ORIGINAL=" << cleanSrcName << endl << endl; | |
} | |
fileStream << "include(" << relativeIncludeFilePath << ")" << endl << endl; | |
} else if (suffix == "h") { | |
fileStream << "#include \"" << relativeIncludeFilePath << "\"" << endl << endl; | |
} | |
fileStream.flush(); | |
currentFile.close(); | |
} | |
} else { | |
intermediate = QFile::copy(entry.absoluteFilePath(), destFile); | |
SetFileAttributes((wchar_t*)destFile.utf16(), FILE_ATTRIBUTE_NORMAL); | |
} | |
} | |
if(!intermediate) { | |
qDebug() << "cpdir: Failure for " << entry.fileName() << entry.isDir(); | |
result = false; | |
} | |
} | |
return result; | |
} | |
bool Environment::rmdir(const QString &name) | |
{ | |
bool result = true; | |
QString cleanName = QDir::cleanPath(name); | |
QDir dir = QDir(cleanName); | |
QFileInfoList allEntries = dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot); | |
for (int i = 0; result && (i < allEntries.count()); ++i) { | |
QFileInfo entry = allEntries.at(i); | |
if (entry.isDir()) { | |
result &= rmdir(entry.absoluteFilePath()); | |
} else { | |
result &= QFile::remove(entry.absoluteFilePath()); | |
} | |
} | |
result &= dir.rmdir(cleanName); | |
return result; | |
} | |
QString Environment::symbianEpocRoot() | |
{ | |
// Call function defined in tools/shared/symbian/epocroot_p.h | |
return ::qt_epocRoot(); | |
} | |
QT_END_NAMESPACE |