tools/porting/src/fileporter.cpp
changeset 0 1918ee327afb
child 4 3b1da2848fc7
equal deleted inserted replaced
-1:000000000000 0:1918ee327afb
       
     1 /****************************************************************************
       
     2 **
       
     3 ** Copyright (C) 2009 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 qt3to4 porting application 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 "preprocessorcontrol.h"
       
    43 #include "fileporter.h"
       
    44 #include "replacetoken.h"
       
    45 #include "logger.h"
       
    46 #include "tokenizer.h"
       
    47 #include "filewriter.h"
       
    48 #include <QFile>
       
    49 #include <QDir>
       
    50 #include <QFileInfo>
       
    51 #include <QHash>
       
    52 #include <QtDebug>
       
    53 
       
    54 QT_BEGIN_NAMESPACE
       
    55 
       
    56 using namespace TokenEngine;
       
    57 using namespace Rpp;
       
    58 
       
    59 FilePorter::FilePorter(PreprocessorCache &preprocessorCache)
       
    60 :preprocessorCache(preprocessorCache)
       
    61 ,tokenReplacementRules(PortingRules::instance()->getTokenReplacementRules())
       
    62 ,headerReplacements(PortingRules::instance()->getHeaderReplacements())
       
    63 ,replaceToken(tokenReplacementRules)
       
    64 {
       
    65     foreach (const QString &headerName, PortingRules::instance()->getHeaderList(PortingRules::Qt4)) {
       
    66         qt4HeaderNames.insert(headerName.toLatin1());
       
    67     }
       
    68 }
       
    69 
       
    70 /*
       
    71     Ports a file given by fileName, which should be an absolute file path.
       
    72 */
       
    73 void FilePorter::port(QString fileName)
       
    74 {
       
    75     // Get file tokens from cache.
       
    76     TokenContainer sourceTokens = preprocessorCache.sourceTokens(fileName);
       
    77     if(sourceTokens.count() == 0)
       
    78         return;
       
    79 
       
    80     Logger::instance()->beginSection();
       
    81 
       
    82     // Get include directive replacements.
       
    83     const Rpp::Source * source = preprocessorCache.sourceTree(fileName);
       
    84     PreprocessReplace preprocessReplace(source, PortingRules::instance()->getHeaderReplacements());
       
    85     TextReplacements sourceReplacements = preprocessReplace.getReplacements();
       
    86 
       
    87     // Get token replacements.
       
    88     sourceReplacements += replaceToken.getTokenTextReplacements(sourceTokens);
       
    89 
       
    90     // Apply the replacements to the source text.
       
    91     QByteArray portedContents = sourceReplacements.apply(sourceTokens.fullText());
       
    92 
       
    93     // Add include directives for classes that are no longer implicitly
       
    94     // included via other headers. This step needs to be done after the token
       
    95     // replacements, since we need to know which new class names that has been
       
    96     // inserted in the source.
       
    97     portedContents = includeAnalyse(portedContents);
       
    98 
       
    99     // Check if any changes has been made.
       
   100     if(portedContents == sourceTokens.fullText()) {
       
   101         Logger::instance()->addEntry(
       
   102             new PlainLogEntry(QLatin1String("Info"), QLatin1String("Porting"),  QLatin1String("No changes made to file ") + fileName));
       
   103         Logger::instance()->commitSection();
       
   104         return;
       
   105     }
       
   106 
       
   107     // Write file, commit log if write was successful.
       
   108     FileWriter::WriteResult result = FileWriter::instance()->writeFileVerbously(fileName, portedContents);
       
   109     Logger *logger = Logger::instance();
       
   110     if (result == FileWriter::WriteSucceeded) {
       
   111         logger->commitSection();
       
   112     } else if (result == FileWriter::WriteFailed) {
       
   113         logger->revertSection();
       
   114         logger->addEntry(
       
   115             new PlainLogEntry(QLatin1String("Error"), QLatin1String("Porting"),  QLatin1String("Error writing to file ") + fileName));
       
   116     } else if (result == FileWriter::WriteSkipped) {
       
   117         logger->revertSection();
       
   118         logger->addEntry(
       
   119             new PlainLogEntry(QLatin1String("Error"), QLatin1String("Porting"),  QLatin1String("User skipped file ") + fileName));
       
   120     } else {
       
   121         // Internal error.
       
   122         logger->revertSection();
       
   123         const QString errorString = QLatin1String("Internal error in qt3to4 - FileWriter returned invalid result code while writing to ") + fileName;
       
   124         logger->addEntry(new PlainLogEntry(QLatin1String("Error"), QLatin1String("Porting"), errorString));
       
   125     }
       
   126 }
       
   127 
       
   128 QSet<QByteArray> FilePorter::usedQtModules()
       
   129 {
       
   130     return m_usedQtModules;
       
   131 }
       
   132 
       
   133 TextReplacements FilePorter::includeDirectiveReplacements()
       
   134 {
       
   135     return TextReplacements();
       
   136 }
       
   137 
       
   138 QByteArray FilePorter::includeAnalyse(QByteArray fileContents)
       
   139 {
       
   140     // Tokenize file contents agein, since it has changed.
       
   141     QVector<TokenEngine::Token> tokens  = tokenizer.tokenize(fileContents);
       
   142     TokenEngine::TokenContainer tokenContainer(fileContents, tokens);
       
   143     IncludeDirectiveAnalyzer includeDirectiveAnalyzer(tokenContainer);
       
   144 
       
   145     // Get list of used classes.
       
   146     QSet<QByteArray> classes = includeDirectiveAnalyzer.usedClasses();
       
   147 
       
   148     // Iterate class list and find which modules are used. This info is used elswhere
       
   149     // by when porting the .pro file.
       
   150     const QHash<QByteArray, QByteArray> classLibraryList = PortingRules::instance()->getClassLibraryList();
       
   151     foreach (const QByteArray &className, classes) {
       
   152         m_usedQtModules.insert(classLibraryList.value(className));
       
   153     }
       
   154 
       
   155     // Get list of included headers.
       
   156     QSet<QByteArray> headers = includeDirectiveAnalyzer.includedHeaders();
       
   157 
       
   158     // Find classes that is missing an include directive and that has a needHeader rule.
       
   159     const QHash<QByteArray, QByteArray> neededHeaders = PortingRules::instance()->getNeededHeaders();
       
   160     QList<QByteArray> insertHeaders;
       
   161     foreach (const QByteArray &className, classes) {
       
   162         if (!headers.contains((className.toLower() + QByteArray(".h"))) &&
       
   163             !headers.contains(className)) {
       
   164             const QByteArray insertHeader = neededHeaders.value(className);
       
   165             if (insertHeader != QByteArray())
       
   166                 insertHeaders.append((QByteArray("#include <") + insertHeader + QByteArray(">")));
       
   167         }
       
   168     }
       
   169 
       
   170     const QByteArray lineEnding = detectLineEndings(fileContents);
       
   171     
       
   172     // Insert include directives undeclared classes.
       
   173     int insertCount = insertHeaders.count();
       
   174     if (insertCount > 0) {
       
   175         QByteArray insertText;
       
   176         QByteArray logText;
       
   177 
       
   178         insertText += QByteArray("//Added by qt3to4:") + lineEnding;
       
   179         logText += QByteArray("In file ");
       
   180         logText += Logger::instance()->globalState.value(QLatin1String("currentFileName")).toLocal8Bit();
       
   181         logText += QByteArray(": Added the following include directives:\n");
       
   182         foreach (const QByteArray &headerName, insertHeaders) {
       
   183             insertText = insertText + headerName + lineEnding;
       
   184             logText += QByteArray("\t");
       
   185             logText += headerName + QByteArray(" ");
       
   186         }
       
   187 
       
   188         const int insertLine = 0;
       
   189         Logger::instance()->updateLineNumbers(insertLine, insertCount + 1);
       
   190         const int insertPos = includeDirectiveAnalyzer.insertPos();
       
   191         fileContents.insert(insertPos, insertText);
       
   192         Logger::instance()->addEntry(new PlainLogEntry(QLatin1String("Info"), QLatin1String("Porting"), QString::fromLatin1(logText.constData())));
       
   193     }
       
   194 
       
   195     return fileContents;
       
   196 }
       
   197 
       
   198 PreprocessReplace::PreprocessReplace(const Rpp::Source *source, const QHash<QByteArray, QByteArray> &headerReplacements)
       
   199 :headerReplacements(headerReplacements)
       
   200 {
       
   201     // Walk preprocessor tree.
       
   202     evaluateItem(source);
       
   203 }
       
   204 
       
   205 TextReplacements PreprocessReplace::getReplacements()
       
   206 {
       
   207     return replacements;
       
   208 }
       
   209 /*
       
   210     Replaces headers no longer present with support headers.
       
   211 */
       
   212 void PreprocessReplace::evaluateIncludeDirective(const Rpp::IncludeDirective *directive)
       
   213 {
       
   214     const QByteArray headerPathName = directive->filename();
       
   215     const TokenEngine::TokenList headerPathTokens = directive->filenameTokens();
       
   216 
       
   217     // Get the file name part of the file path.
       
   218     const QByteArray headerFileName = QFileInfo(QString::fromLatin1(headerPathName.constData())).fileName().toUtf8();
       
   219 
       
   220     // Check if we should replace the filename.
       
   221     QByteArray replacement = headerReplacements.value(headerFileName);
       
   222 
       
   223     // Also check lower-case version to catch incorrectly capitalized file names on Windows.
       
   224     if (replacement.isEmpty())
       
   225         replacement = headerReplacements.value(headerFileName.toLower());
       
   226 
       
   227     const int numTokens = headerPathTokens.count();
       
   228     if (numTokens > 0 && !replacement.isEmpty()) {
       
   229         // Here we assume that the last token contains a part of the file name.
       
   230         const TokenEngine::Token lastToken = headerPathTokens.token(numTokens -1);
       
   231         int endPos = lastToken.start + lastToken.length;
       
   232         // If the file name is specified in quotes, then the quotes will be a part
       
   233         // of the token. Decrement endpos to leave out the ending quote when replacing.
       
   234         if (directive->includeType() == IncludeDirective::QuoteInclude)
       
   235             --endPos;
       
   236         const int length = headerFileName.count();
       
   237         const int startPos = endPos - length;
       
   238         replacements.insert(replacement, startPos, length);
       
   239         addLogSourceEntry(QString::fromLatin1((headerFileName + QByteArray(" -> ") + replacement).constData()),
       
   240                           headerPathTokens.tokenContainer(0), headerPathTokens.containerIndex(0));
       
   241     }
       
   242 }
       
   243 
       
   244 /*
       
   245     Replace line comments containing MOC_SKIP_BEGIN with #ifdef Q_MOC_RUN and MOC_SKIP_END with #endif
       
   246 */
       
   247 void PreprocessReplace::evaluateText(const Rpp::Text *textLine)
       
   248 {
       
   249     if (textLine->count() < 1)
       
   250         return;
       
   251  
       
   252     const TokenEngine::TokenContainer container = textLine->text().tokenContainer(0);
       
   253     foreach (Rpp::Token *token, textLine->tokenList()) {
       
   254         if (token->toLineComment()) {
       
   255             const int tokenIndex = token->index();
       
   256             const QByteArray text = container.text(tokenIndex);
       
   257             const TokenEngine::Token containerToken = container.token(tokenIndex);
       
   258            
       
   259             if (text.contains(QByteArray("MOC_SKIP_BEGIN"))) {
       
   260                 replacements.insert(QByteArray("#ifndef Q_MOC_RUN"), containerToken.start, containerToken.length);
       
   261                 addLogSourceEntry(QLatin1String("MOC_SKIP_BEGIN -> #ifndef Q_MOC_RUN"), container, tokenIndex);
       
   262             }        
       
   263             if (text.contains(QByteArray("MOC_SKIP_END"))) {
       
   264                 replacements.insert(QByteArray("#endif"), containerToken.start, containerToken.length);
       
   265                 addLogSourceEntry(QLatin1String("MOC_SKIP_END -> #endif"), container, tokenIndex);
       
   266             }
       
   267         }
       
   268     }
       
   269 }
       
   270 
       
   271 IncludeDirectiveAnalyzer::IncludeDirectiveAnalyzer(const TokenEngine::TokenContainer &fileContents)
       
   272 :fileContents(fileContents)
       
   273 {
       
   274     const QVector<Type> lexical = RppLexer().lex(fileContents);
       
   275     source = Preprocessor().parse(fileContents, lexical, &mempool);
       
   276     foundInsertPos = false;
       
   277     foundQtHeader = false;
       
   278     ifSectionCount = 0;
       
   279     insertTokenIndex = 0;
       
   280 
       
   281     evaluateItem(source);
       
   282 }
       
   283 
       
   284 /*
       
   285     Returns a position indicating where new include directives should be inserted.
       
   286 */
       
   287 int IncludeDirectiveAnalyzer::insertPos()
       
   288 {
       
   289     const TokenEngine::Token insertToken = fileContents.token(insertTokenIndex);
       
   290     return insertToken.start;
       
   291 }
       
   292 
       
   293 /*
       
   294     Returns a set of all headers included from this file.
       
   295 */
       
   296 QSet<QByteArray> IncludeDirectiveAnalyzer::includedHeaders()
       
   297 {
       
   298     return m_includedHeaders;
       
   299 }
       
   300 
       
   301 /*
       
   302     Returns a list of used Qt classes.
       
   303 */
       
   304 QSet<QByteArray> IncludeDirectiveAnalyzer::usedClasses()
       
   305 {
       
   306     return m_usedClasses;
       
   307 }
       
   308 
       
   309 /*
       
   310     Set insetionTokenindex to a token near other #include directives, preferably
       
   311     just after a block of include directives that includes other Qt headers.
       
   312 */
       
   313 void IncludeDirectiveAnalyzer::evaluateIncludeDirective(const IncludeDirective *directive)
       
   314 {
       
   315     const QByteArray filename = directive->filename();
       
   316     if (filename.isEmpty())
       
   317         return;
       
   318 
       
   319     m_includedHeaders.insert(filename);
       
   320 
       
   321     if (foundInsertPos || ifSectionCount > 1)
       
   322         return;
       
   323 
       
   324     const bool isQtHeader = (filename.at(0) == 'q' || filename.at(0) == 'Q');
       
   325     if (!isQtHeader && foundQtHeader) {
       
   326         foundInsertPos = true;
       
   327         return;
       
   328     }
       
   329 
       
   330     if (isQtHeader)
       
   331         foundQtHeader = true;
       
   332 
       
   333     // Get the last token for this directive.
       
   334     TokenEngine::TokenSection tokenSection = directive->text();
       
   335     const int newLineToken = 1;
       
   336     insertTokenIndex = tokenSection.containerIndex(tokenSection.count() - 1) + newLineToken;
       
   337 }
       
   338 
       
   339 /*
       
   340     Avoid inserting inside IfSections, except in the first one
       
   341     we see, which probably is the header multiple inclusion guard.
       
   342 */
       
   343 void IncludeDirectiveAnalyzer::evaluateIfSection(const IfSection *ifSection)
       
   344 {
       
   345     ++ifSectionCount;
       
   346     RppTreeWalker::evaluateIfSection(ifSection);
       
   347     --ifSectionCount;
       
   348 }
       
   349 
       
   350 /*
       
   351     Read all IdTokens and look for Qt class names.  Also, on
       
   352     the first IdToken set foundInsertPos to true
       
   353 
       
   354 */
       
   355 void IncludeDirectiveAnalyzer::evaluateText(const Text *textLine)
       
   356 {
       
   357     const int numTokens = textLine->count();
       
   358     for (int t = 0; t < numTokens; ++t) {
       
   359         Rpp::Token *token = textLine->token(t);
       
   360         if (token->toIdToken()) {
       
   361             foundInsertPos = true;
       
   362             const int containerIndex = token->index();
       
   363             const QByteArray tokenText = fileContents.text(containerIndex);
       
   364             m_usedClasses.insert(tokenText);
       
   365         }
       
   366     }
       
   367 }
       
   368 
       
   369 QT_END_NAMESPACE