util/tools/configure/environment.cpp
changeset 7 f7bc934e204c
equal deleted inserted replaced
3:41300fa6a67c 7:f7bc934e204c
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
       
     4 ** All rights reserved.
       
     5 ** Contact: Nokia Corporation (qt-info@nokia.com)
       
     6 **
       
     7 ** This file is part of the tools applications of the Qt Toolkit.
       
     8 **
       
     9 ** $QT_BEGIN_LICENSE:LGPL$
       
    10 ** No Commercial Usage
       
    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 ** 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 ** If you have questions regarding the use of this file, please contact
       
    29 ** Nokia at qt-info@nokia.com.
       
    30 **
       
    31 **
       
    32 **
       
    33 **
       
    34 **
       
    35 **
       
    36 **
       
    37 **
       
    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 
       
    57 using namespace std;
       
    58 
       
    59 #ifdef Q_OS_WIN32
       
    60 #include <qt_windows.h>
       
    61 #endif
       
    62 
       
    63 #include <symbian/epocroot.h> // from tools/shared
       
    64 #include <windows/registry.h> // from tools/shared
       
    65 
       
    66 QT_BEGIN_NAMESPACE
       
    67 
       
    68 struct 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, "mingw32-gcc.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_UNKNOWN, "Unknown", 0, 0},
       
    84 };
       
    85 
       
    86 
       
    87 // Initialize static variables
       
    88 Compiler Environment::detectedCompiler = CC_UNKNOWN;
       
    89 
       
    90 /*!
       
    91     Returns the pointer to the CompilerInfo for a \a compiler.
       
    92 */
       
    93 CompilerInfo *Environment::compilerInfo(Compiler compiler)
       
    94 {
       
    95     int i = 0;
       
    96     while(compiler_info[i].compiler != compiler && compiler_info[i].compiler != CC_UNKNOWN)
       
    97         ++i;
       
    98     return &(compiler_info[i]);
       
    99 }
       
   100 
       
   101 /*!
       
   102     Returns the qmakespec for the compiler detected on the system.
       
   103 */
       
   104 QString Environment::detectQMakeSpec()
       
   105 {
       
   106     QString spec;
       
   107     switch (detectCompiler()) {
       
   108     case CC_NET2008:
       
   109         spec = "win32-msvc2008";
       
   110         break;
       
   111     case CC_NET2005:
       
   112         spec = "win32-msvc2005";
       
   113         break;
       
   114     case CC_NET2003:
       
   115         spec = "win32-msvc2003";
       
   116         break;
       
   117     case CC_NET2002:
       
   118         spec = "win32-msvc2002";
       
   119         break;
       
   120     case CC_MSVC4:
       
   121     case CC_MSVC5:
       
   122     case CC_MSVC6:
       
   123         spec = "win32-msvc";
       
   124         break;
       
   125     case CC_INTEL:
       
   126         spec = "win32-icc";
       
   127         break;
       
   128     case CC_MINGW:
       
   129         spec = "win32-g++";
       
   130         break;
       
   131     case CC_BORLAND:
       
   132         spec = "win32-borland";
       
   133         break;
       
   134     default:
       
   135         break;
       
   136     }
       
   137 
       
   138     return spec;
       
   139 }
       
   140 
       
   141 /*!
       
   142     Returns the enum of the compiler which was detected on the system.
       
   143     The compilers are detected in the order as entered into the
       
   144     compiler_info list.
       
   145 
       
   146     If more than one compiler is found, CC_UNKNOWN is returned.
       
   147 */
       
   148 Compiler Environment::detectCompiler()
       
   149 {
       
   150 #ifndef Q_OS_WIN32
       
   151     return MSVC6; // Always generate MSVC 6.0 versions on other platforms
       
   152 #else
       
   153     if(detectedCompiler != CC_UNKNOWN)
       
   154         return detectedCompiler;
       
   155 
       
   156     int installed = 0;
       
   157 
       
   158     // Check for compilers in registry first, to see which version is in PATH
       
   159     QString paths = qgetenv("PATH");
       
   160     QStringList pathlist = paths.toLower().split(";");
       
   161     for(int i = 0; compiler_info[i].compiler; ++i) {
       
   162         QString productPath = readRegistryKey(HKEY_LOCAL_MACHINE, compiler_info[i].regKey).toLower();
       
   163         if (productPath.length()) {
       
   164             QStringList::iterator it;
       
   165             for(it = pathlist.begin(); it != pathlist.end(); ++it) {
       
   166                 if((*it).contains(productPath)) {
       
   167                     ++installed;
       
   168                     detectedCompiler = compiler_info[i].compiler;
       
   169                     break;
       
   170                 }
       
   171             }
       
   172         }
       
   173     }
       
   174 
       
   175     // Now just go looking for the executables, and accept any executable as the lowest version
       
   176     if (!installed) {
       
   177         for(int i = 0; compiler_info[i].compiler; ++i) {
       
   178             QString executable = QString(compiler_info[i].executable).toLower();
       
   179             if (executable.length() && Environment::detectExecutable(executable)) {
       
   180                 ++installed;
       
   181                 detectedCompiler = compiler_info[i].compiler;
       
   182                 break;
       
   183             }
       
   184         }
       
   185     }
       
   186 
       
   187     if (installed > 1) {
       
   188         cout << "Found more than one known compiler! Using \"" << compilerInfo(detectedCompiler)->compilerStr << "\"" << endl;
       
   189         detectedCompiler = CC_UNKNOWN;
       
   190     }
       
   191     return detectedCompiler;
       
   192 #endif
       
   193 };
       
   194 
       
   195 /*!
       
   196     Returns true if the \a executable could be loaded, else false.
       
   197     This means that the executable either is in the current directory
       
   198     or in the PATH.
       
   199 */
       
   200 bool Environment::detectExecutable(const QString &executable)
       
   201 {
       
   202     PROCESS_INFORMATION procInfo;
       
   203     memset(&procInfo, 0, sizeof(procInfo));
       
   204 
       
   205     STARTUPINFO startInfo;
       
   206     memset(&startInfo, 0, sizeof(startInfo));
       
   207     startInfo.cb = sizeof(startInfo);
       
   208 
       
   209     bool couldExecute = CreateProcess(0, (wchar_t*)executable.utf16(),
       
   210                                       0, 0, false,
       
   211                                       CREATE_NO_WINDOW | CREATE_SUSPENDED,
       
   212                                       0, 0, &startInfo, &procInfo);
       
   213 
       
   214     if (couldExecute) {
       
   215         CloseHandle(procInfo.hThread);
       
   216         TerminateProcess(procInfo.hProcess, 0);
       
   217         CloseHandle(procInfo.hProcess);
       
   218     }
       
   219     return couldExecute;
       
   220 }
       
   221 
       
   222 /*!
       
   223     Creates a commandling from \a program and it \a arguments,
       
   224     escaping characters that needs it.
       
   225 */
       
   226 static QString qt_create_commandline(const QString &program, const QStringList &arguments)
       
   227 {
       
   228     QString programName = program;
       
   229     if (!programName.startsWith("\"") && !programName.endsWith("\"") && programName.contains(" "))
       
   230         programName = "\"" + programName + "\"";
       
   231     programName.replace("/", "\\");
       
   232 
       
   233     QString args;
       
   234     // add the prgram as the first arrg ... it works better
       
   235     args = programName + " ";
       
   236     for (int i=0; i<arguments.size(); ++i) {
       
   237         QString tmp = arguments.at(i);
       
   238         // in the case of \" already being in the string the \ must also be escaped
       
   239         tmp.replace( "\\\"", "\\\\\"" );
       
   240         // escape a single " because the arguments will be parsed
       
   241         tmp.replace( "\"", "\\\"" );
       
   242         if (tmp.isEmpty() || tmp.contains(' ') || tmp.contains('\t')) {
       
   243             // The argument must not end with a \ since this would be interpreted
       
   244             // as escaping the quote -- rather put the \ behind the quote: e.g.
       
   245             // rather use "foo"\ than "foo\"
       
   246             QString endQuote("\"");
       
   247             int i = tmp.length();
       
   248             while (i>0 && tmp.at(i-1) == '\\') {
       
   249                 --i;
       
   250                 endQuote += "\\";
       
   251             }
       
   252             args += QString(" \"") + tmp.left(i) + endQuote;
       
   253         } else {
       
   254             args += ' ' + tmp;
       
   255         }
       
   256     }
       
   257     return args;
       
   258 }
       
   259 
       
   260 /*!
       
   261     Creates a QByteArray of the \a environment.
       
   262 */
       
   263 static QByteArray qt_create_environment(const QStringList &environment)
       
   264 {
       
   265     QByteArray envlist;
       
   266     if (environment.isEmpty())
       
   267         return envlist;
       
   268 
       
   269     int pos = 0;
       
   270     // add PATH if necessary (for DLL loading)
       
   271     QByteArray path = qgetenv("PATH");
       
   272     if (environment.filter(QRegExp("^PATH=",Qt::CaseInsensitive)).isEmpty() && !path.isNull()) {
       
   273             QString tmp = QString(QLatin1String("PATH=%1")).arg(QString::fromLocal8Bit(path));
       
   274             uint tmpSize = sizeof(wchar_t) * (tmp.length() + 1);
       
   275             envlist.resize(envlist.size() + tmpSize);
       
   276             memcpy(envlist.data() + pos, tmp.utf16(), tmpSize);
       
   277             pos += tmpSize;
       
   278     }
       
   279     // add the user environment
       
   280     for (QStringList::ConstIterator it = environment.begin(); it != environment.end(); it++ ) {
       
   281             QString tmp = *it;
       
   282             uint tmpSize = sizeof(wchar_t) * (tmp.length() + 1);
       
   283             envlist.resize(envlist.size() + tmpSize);
       
   284             memcpy(envlist.data() + pos, tmp.utf16(), tmpSize);
       
   285             pos += tmpSize;
       
   286     }
       
   287     // add the 2 terminating 0 (actually 4, just to be on the safe side)
       
   288     envlist.resize(envlist.size() + 4);
       
   289     envlist[pos++] = 0;
       
   290     envlist[pos++] = 0;
       
   291     envlist[pos++] = 0;
       
   292     envlist[pos++] = 0;
       
   293 
       
   294     return envlist;
       
   295 }
       
   296 
       
   297 /*!
       
   298     Executes the command described in \a arguments, in the
       
   299     environment inherited from the parent process, with the
       
   300     \a additionalEnv settings applied.
       
   301     \a removeEnv removes the specified environment variables from
       
   302     the environment of the executed process.
       
   303 
       
   304     Returns the exit value of the process, or -1 if the command could
       
   305     not be executed.
       
   306 
       
   307     This function uses _(w)spawnvpe to spawn a process by searching
       
   308     through the PATH environment variable.
       
   309 */
       
   310 int Environment::execute(QStringList arguments, const QStringList &additionalEnv, const QStringList &removeEnv)
       
   311 {
       
   312 #ifdef CONFIGURE_DEBUG_EXECUTE
       
   313     qDebug() << "About to Execute: " << arguments;
       
   314     qDebug() << "   " << QDir::currentPath();
       
   315     qDebug() << "   " << additionalEnv;
       
   316     qDebug() << "   " << removeEnv;
       
   317 #endif
       
   318     // Create the full environment from the current environment and
       
   319     // the additionalEnv strings, then remove all variables defined
       
   320     // in removeEnv
       
   321     QMap<QString, QString> fullEnvMap;
       
   322     LPWSTR envStrings = GetEnvironmentStrings();
       
   323     if (envStrings) {
       
   324         int strLen = 0;
       
   325         for (LPWSTR envString = envStrings; *(envString); envString += strLen + 1) {
       
   326             strLen = wcslen(envString);
       
   327             QString str = QString((const QChar*)envString, strLen);
       
   328             if (!str.startsWith("=")) { // These are added by the system
       
   329                 int sepIndex = str.indexOf('=');
       
   330                 fullEnvMap.insert(str.left(sepIndex).toUpper(), str.mid(sepIndex +1));
       
   331             }
       
   332         }
       
   333     }
       
   334     FreeEnvironmentStrings(envStrings);
       
   335 
       
   336     // Add additionalEnv variables
       
   337     for (int i = 0; i < additionalEnv.count(); ++i) {
       
   338         const QString &str = additionalEnv.at(i);
       
   339         int sepIndex = str.indexOf('=');
       
   340         fullEnvMap.insert(str.left(sepIndex).toUpper(), str.mid(sepIndex +1));
       
   341     }
       
   342 
       
   343     // Remove removeEnv variables
       
   344     for (int j = 0; j < removeEnv.count(); ++j)
       
   345         fullEnvMap.remove(removeEnv.at(j).toUpper());
       
   346 
       
   347     // Add all variables to a QStringList
       
   348     QStringList fullEnv;
       
   349     QMapIterator<QString, QString> it(fullEnvMap);
       
   350     while (it.hasNext()) {
       
   351         it.next();
       
   352         fullEnv += QString(it.key() + "=" + it.value());
       
   353     }
       
   354 
       
   355     // ----------------------------
       
   356     QString program = arguments.takeAt(0);
       
   357     QString args = qt_create_commandline(program, arguments);
       
   358     QByteArray envlist = qt_create_environment(fullEnv);
       
   359 
       
   360     DWORD exitCode = -1;
       
   361     PROCESS_INFORMATION procInfo;
       
   362     memset(&procInfo, 0, sizeof(procInfo));
       
   363 
       
   364     STARTUPINFO startInfo;
       
   365     memset(&startInfo, 0, sizeof(startInfo));
       
   366     startInfo.cb = sizeof(startInfo);
       
   367 
       
   368     bool couldExecute = CreateProcess(0, (wchar_t*)args.utf16(),
       
   369                                       0, 0, true, CREATE_UNICODE_ENVIRONMENT,
       
   370                                       envlist.isEmpty() ? 0 : envlist.data(),
       
   371                                       0, &startInfo, &procInfo);
       
   372 
       
   373     if (couldExecute) {
       
   374         WaitForSingleObject(procInfo.hProcess, INFINITE);
       
   375         GetExitCodeProcess(procInfo.hProcess, &exitCode);
       
   376         CloseHandle(procInfo.hThread);
       
   377         CloseHandle(procInfo.hProcess);
       
   378     }
       
   379 
       
   380 
       
   381     if (exitCode == -1) {
       
   382         switch(GetLastError()) {
       
   383         case E2BIG:
       
   384             cerr << "execute: Argument list exceeds 1024 bytes" << endl;
       
   385             foreach(QString arg, arguments)
       
   386                 cerr << "   (" << arg.toLocal8Bit().constData() << ")" << endl;
       
   387             break;
       
   388         case ENOENT:
       
   389             cerr << "execute: File or path is not found (" << program.toLocal8Bit().constData() << ")" << endl;
       
   390             break;
       
   391         case ENOEXEC:
       
   392             cerr << "execute: Specified file is not executable or has invalid executable-file format (" << program.toLocal8Bit().constData() << ")" << endl;
       
   393             break;
       
   394         case ENOMEM:
       
   395             cerr << "execute: Not enough memory is available to execute new process." << endl;
       
   396             break;
       
   397         default:
       
   398             cerr << "execute: Unknown error" << endl;
       
   399             foreach(QString arg, arguments)
       
   400                 cerr << "   (" << arg.toLocal8Bit().constData() << ")" << endl;
       
   401             break;
       
   402         }
       
   403     }
       
   404     return exitCode;
       
   405 }
       
   406 
       
   407 bool Environment::cpdir(const QString &srcDir, const QString &destDir)
       
   408 {
       
   409     QString cleanSrcName = QDir::cleanPath(srcDir);
       
   410     QString cleanDstName = QDir::cleanPath(destDir);
       
   411 #ifdef CONFIGURE_DEBUG_CP_DIR
       
   412     qDebug() << "Attempt to cpdir " << cleanSrcName << "->" << cleanDstName;
       
   413 #endif
       
   414     if(!QFile::exists(cleanDstName) && !QDir().mkpath(cleanDstName)) {
       
   415 	qDebug() << "cpdir: Failure to create " << cleanDstName;
       
   416 	return false;
       
   417     }
       
   418 
       
   419     bool result = true;
       
   420     QDir dir = QDir(cleanSrcName);
       
   421     QFileInfoList allEntries = dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
       
   422     for (int i = 0; result && (i < allEntries.count()); ++i) {
       
   423         QFileInfo entry = allEntries.at(i);
       
   424 	bool intermediate = true;
       
   425         if (entry.isDir()) {
       
   426             intermediate = cpdir(QString("%1/%2").arg(cleanSrcName).arg(entry.fileName()),
       
   427                             QString("%1/%2").arg(cleanDstName).arg(entry.fileName()));
       
   428         } else {
       
   429             QString destFile = QString("%1/%2").arg(cleanDstName).arg(entry.fileName());
       
   430 #ifdef CONFIGURE_DEBUG_CP_DIR
       
   431 	    qDebug() << "About to cp (file)" << entry.absoluteFilePath() << "->" << destFile;
       
   432 #endif
       
   433 	    QFile::remove(destFile);
       
   434             intermediate = QFile::copy(entry.absoluteFilePath(), destFile);
       
   435             SetFileAttributes((wchar_t*)destFile.utf16(), FILE_ATTRIBUTE_NORMAL);
       
   436         }
       
   437 	if(!intermediate) {
       
   438 	    qDebug() << "cpdir: Failure for " << entry.fileName() << entry.isDir();
       
   439 	    result = false;
       
   440 	}
       
   441     }
       
   442     return result;
       
   443 }
       
   444 
       
   445 bool Environment::rmdir(const QString &name)
       
   446 {
       
   447     bool result = true;
       
   448     QString cleanName = QDir::cleanPath(name);
       
   449 
       
   450     QDir dir = QDir(cleanName);
       
   451     QFileInfoList allEntries = dir.entryInfoList(QDir::AllDirs | QDir::Files | QDir::NoDotAndDotDot);
       
   452     for (int i = 0; result && (i < allEntries.count()); ++i) {
       
   453         QFileInfo entry = allEntries.at(i);
       
   454         if (entry.isDir()) {
       
   455             result &= rmdir(entry.absoluteFilePath());
       
   456         } else {
       
   457             result &= QFile::remove(entry.absoluteFilePath());
       
   458         }
       
   459     }
       
   460     result &= dir.rmdir(cleanName);
       
   461     return result;
       
   462 }
       
   463 
       
   464 QString Environment::symbianEpocRoot()
       
   465 {
       
   466     // Call function defined in tools/shared/symbian/epocroot.h
       
   467     return ::epocRoot();
       
   468 }
       
   469 
       
   470 QT_END_NAMESPACE