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 |
|
---|
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
|
---|