source: trunk/tools/porting/src/fileporter.cpp@ 315

Last change on this file since 315 was 2, checked in by Dmitry A. Kuminov, 16 years ago

Initially imported qt-all-opensource-src-4.5.1 from Trolltech.

File size: 14.0 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
4** Contact: Qt Software Information ([email protected])
5**
6** This file is part of the qt3to4 porting application of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial Usage
10** Licensees holding valid Qt Commercial licenses may use this file in
11** accordance with the Qt Commercial License Agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and Nokia.
14**
15** GNU Lesser General Public License Usage
16** Alternatively, this file may be used under the terms of the GNU Lesser
17** General Public License version 2.1 as published by the Free Software
18** Foundation and appearing in the file LICENSE.LGPL included in the
19** packaging of this file. Please review the following information to
20** ensure the GNU Lesser General Public License version 2.1 requirements
21** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
22**
23** In addition, as a special exception, Nokia gives you certain
24** additional rights. These rights are described in the Nokia Qt LGPL
25** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
26** 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 are unsure which license is appropriate for your use, please
37** contact the sales department at [email protected].
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
54QT_BEGIN_NAMESPACE
55
56using namespace TokenEngine;
57using namespace Rpp;
58
59FilePorter::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*/
73void 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
128QSet<QByteArray> FilePorter::usedQtModules()
129{
130 return m_usedQtModules;
131}
132
133TextReplacements FilePorter::includeDirectiveReplacements()
134{
135 return TextReplacements();
136}
137
138QByteArray 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
198PreprocessReplace::PreprocessReplace(const Rpp::Source *source, const QHash<QByteArray, QByteArray> &headerReplacements)
199:headerReplacements(headerReplacements)
200{
201 // Walk preprocessor tree.
202 evaluateItem(source);
203}
204
205TextReplacements PreprocessReplace::getReplacements()
206{
207 return replacements;
208}
209/*
210 Replaces headers no longer present with support headers.
211*/
212void 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*/
247void 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
271IncludeDirectiveAnalyzer::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*/
287int 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*/
296QSet<QByteArray> IncludeDirectiveAnalyzer::includedHeaders()
297{
298 return m_includedHeaders;
299}
300
301/*
302 Returns a list of used Qt classes.
303*/
304QSet<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*/
313void 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*/
343void 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*/
355void 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
369QT_END_NAMESPACE
Note: See TracBrowser for help on using the repository browser.