1 /****************************************************************************
2 **
3 ** Copyright (C) 2017 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of Qt Creator.
7 **
8 ** Commercial License Usage
9 ** Licensees holding valid commercial Qt licenses may use this file in
10 ** accordance with the commercial license agreement provided with the
11 ** Software or, alternatively, in accordance with the terms contained in
12 ** a written agreement between you and The Qt Company. For licensing terms
13 ** and conditions see https://www.qt.io/terms-conditions. For further
14 ** information use the contact form at https://www.qt.io/contact-us.
15 **
16 ** GNU General Public License Usage
17 ** Alternatively, this file may be used under the terms of the GNU
18 ** General Public License version 3 as published by the Free Software
19 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
20 ** included in the packaging of this file. Please review the following
21 ** information to ensure the GNU General Public License requirements will
22 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
23 **
24 ****************************************************************************/
25
26 #include "clangbatchfileprocessor.h"
27
28 #include "clangautomationutils.h"
29
30 #include <clangcodemodel/clangeditordocumentprocessor.h>
31
32 #include <coreplugin/editormanager/editormanager.h>
33 #include <coreplugin/editormanager/ieditor.h>
34 #include <coreplugin/icore.h>
35 #include <cpptools/cpptoolsreuse.h>
36 #include <cpptools/cpptoolstestcase.h>
37 #include <cpptools/modelmanagertesthelper.h>
38 #include <cpptools/projectinfo.h>
39 #include <projectexplorer/projectexplorer.h>
40 #include <texteditor/codeassist/assistinterface.h>
41 #include <texteditor/codeassist/assistproposalitem.h>
42 #include <texteditor/codeassist/completionassistprovider.h>
43 #include <texteditor/codeassist/genericproposalmodel.h>
44 #include <texteditor/codeassist/iassistprocessor.h>
45 #include <texteditor/codeassist/iassistproposal.h>
46 #include <texteditor/textdocument.h>
47 #include <texteditor/texteditor.h>
48
49 #include <utils/executeondestruction.h>
50 #include <utils/qtcassert.h>
51
52 #include <QDebug>
53 #include <QElapsedTimer>
54 #include <QFileInfo>
55 #include <QLoggingCategory>
56 #include <QSharedPointer>
57 #include <QString>
58 #include <QThread>
59
60 using namespace ClangBackEnd;
61 using namespace ProjectExplorer;
62
63 namespace ClangCodeModel {
64 namespace Internal {
65
66 static Q_LOGGING_CATEGORY(debug, "qtc.clangcodemodel.batch", QtWarningMsg);
67
timeOutFromEnvironmentVariable()68 static int timeOutFromEnvironmentVariable()
69 {
70 const QByteArray timeoutAsByteArray = qgetenv("QTC_CLANG_BATCH_TIMEOUT");
71
72 bool isConversionOk = false;
73 const int intervalAsInt = timeoutAsByteArray.toInt(&isConversionOk);
74 if (!isConversionOk) {
75 qCDebug(debug, "Environment variable QTC_CLANG_BATCH_TIMEOUT is not set, assuming 30000.");
76 return 30000;
77 }
78
79 return intervalAsInt;
80 }
81
timeOutInMs()82 int timeOutInMs()
83 {
84 static int timeOut = timeOutFromEnvironmentVariable();
85 return timeOut;
86 }
87
88 namespace {
89
90 class BatchFileLineTokenizer
91 {
92 public:
93 BatchFileLineTokenizer(const QString &line);
94
95 QString nextToken();
96
97 private:
98 const QChar *advanceToTokenBegin();
99 const QChar *advanceToTokenEnd();
100
101 bool atEnd() const;
102 bool atWhiteSpace() const;
103 bool atQuotationMark() const;
104
105 private:
106 bool m_isWithinQuotation = false;
107 QString m_line;
108 const QChar *m_currentChar;
109 };
110
BatchFileLineTokenizer(const QString & line)111 BatchFileLineTokenizer::BatchFileLineTokenizer(const QString &line)
112 : m_line(line)
113 , m_currentChar(m_line.unicode())
114 {
115 }
116
nextToken()117 QString BatchFileLineTokenizer::nextToken()
118 {
119 if (const QChar *tokenBegin = advanceToTokenBegin()) {
120 if (const QChar *tokenEnd = advanceToTokenEnd()) {
121 const int length = tokenEnd - tokenBegin;
122 return QString(tokenBegin, length);
123 }
124 }
125
126 return QString();
127 }
128
advanceToTokenBegin()129 const QChar *BatchFileLineTokenizer::advanceToTokenBegin()
130 {
131 m_isWithinQuotation = false;
132
133 forever {
134 if (atEnd())
135 return nullptr;
136
137 if (atQuotationMark()) {
138 m_isWithinQuotation = true;
139 ++m_currentChar;
140 return m_currentChar;
141 }
142
143 if (!atWhiteSpace())
144 return m_currentChar;
145
146 ++m_currentChar;
147 }
148 }
149
advanceToTokenEnd()150 const QChar *BatchFileLineTokenizer::advanceToTokenEnd()
151 {
152 forever {
153 if (m_isWithinQuotation) {
154 if (atEnd()) {
155 qWarning("ClangBatchFileProcessor: error: unfinished quotation.");
156 return nullptr;
157 }
158
159 if (atQuotationMark())
160 return m_currentChar++;
161
162 } else if (atWhiteSpace() || atEnd()) {
163 return m_currentChar;
164 }
165
166 ++m_currentChar;
167 }
168 }
169
atEnd() const170 bool BatchFileLineTokenizer::atEnd() const
171 {
172 return *m_currentChar == QLatin1Char('\0');
173 }
174
atWhiteSpace() const175 bool BatchFileLineTokenizer::atWhiteSpace() const
176 {
177 return *m_currentChar == ' '
178 || *m_currentChar == '\t'
179 || *m_currentChar == '\n';
180 }
181
atQuotationMark() const182 bool BatchFileLineTokenizer::atQuotationMark() const
183 {
184 return *m_currentChar == '"';
185 }
186
187 struct CommandContext {
188 QString filePath;
189 int lineNumber = -1;
190 };
191
192 class Command
193 {
194 public:
195 using Ptr = QSharedPointer<Command>;
196
197 public:
Command(const CommandContext & context)198 Command(const CommandContext &context) : m_commandContext(context) {}
199 virtual ~Command() = default;
200
context() const201 const CommandContext &context() const { return m_commandContext; }
run()202 virtual bool run() { return true; }
203
204 private:
205 const CommandContext m_commandContext;
206 };
207
208 class OpenProjectCommand : public Command
209 {
210 public:
211 OpenProjectCommand(const CommandContext &context,
212 const QString &projectFilePath);
213
214 bool run() override;
215
216 static Command::Ptr parse(BatchFileLineTokenizer &arguments,
217 const CommandContext &context);
218
219 private:
220 QString m_projectFilePath;
221 };
222
OpenProjectCommand(const CommandContext & context,const QString & projectFilePath)223 OpenProjectCommand::OpenProjectCommand(const CommandContext &context,
224 const QString &projectFilePath)
225 : Command(context)
226 , m_projectFilePath(projectFilePath)
227 {
228 }
229
run()230 bool OpenProjectCommand::run()
231 {
232 qCDebug(debug) << "line" << context().lineNumber << "OpenProjectCommand" << m_projectFilePath;
233
234 const ProjectExplorerPlugin::OpenProjectResult openProjectSucceeded
235 = ProjectExplorerPlugin::openProject(m_projectFilePath);
236 QTC_ASSERT(openProjectSucceeded, return false);
237
238 Project *project = openProjectSucceeded.project();
239 project->configureAsExampleProject(nullptr);
240
241 return CppTools::Tests::TestCase::waitUntilProjectIsFullyOpened(project, timeOutInMs());
242 }
243
parse(BatchFileLineTokenizer & arguments,const CommandContext & context)244 Command::Ptr OpenProjectCommand::parse(BatchFileLineTokenizer &arguments,
245 const CommandContext &context)
246 {
247 const QString projectFilePath = arguments.nextToken();
248 if (projectFilePath.isEmpty()) {
249 qWarning("%s:%d: error: No project file path given.",
250 qPrintable(context.filePath),
251 context.lineNumber);
252 return Command::Ptr();
253 }
254
255 const QString absoluteProjectFilePath = QFileInfo(projectFilePath).absoluteFilePath();
256
257 return Command::Ptr(new OpenProjectCommand(context, absoluteProjectFilePath));
258 }
259
260 class OpenDocumentCommand : public Command
261 {
262 public:
263 OpenDocumentCommand(const CommandContext &context,
264 const QString &documentFilePath);
265
266 bool run() override;
267
268 static Command::Ptr parse(BatchFileLineTokenizer &arguments, const CommandContext &context);
269
270 private:
271 QString m_documentFilePath;
272 };
273
OpenDocumentCommand(const CommandContext & context,const QString & documentFilePath)274 OpenDocumentCommand::OpenDocumentCommand(const CommandContext &context,
275 const QString &documentFilePath)
276 : Command(context)
277 , m_documentFilePath(documentFilePath)
278 {
279 }
280
281 class WaitForUpdatedCodeWarnings : public QObject
282 {
283 Q_OBJECT
284
285 public:
286 WaitForUpdatedCodeWarnings(ClangEditorDocumentProcessor *processor);
287
288 bool wait(int timeOutInMs) const;
289
290 private:
onCodeWarningsUpdated()291 void onCodeWarningsUpdated() { m_gotResults = true; }
292
293 private:
294
295 bool m_gotResults = false;
296 };
297
WaitForUpdatedCodeWarnings(ClangEditorDocumentProcessor * processor)298 WaitForUpdatedCodeWarnings::WaitForUpdatedCodeWarnings(ClangEditorDocumentProcessor *processor)
299 {
300 connect(processor,
301 &ClangEditorDocumentProcessor::codeWarningsUpdated,
302 this, &WaitForUpdatedCodeWarnings::onCodeWarningsUpdated);
303 }
304
wait(int timeOutInMs) const305 bool WaitForUpdatedCodeWarnings::wait(int timeOutInMs) const
306 {
307 QElapsedTimer time;
308 time.start();
309
310 forever {
311 if (time.elapsed() > timeOutInMs) {
312 qWarning("WaitForUpdatedCodeWarnings: timeout of %d ms reached.", timeOutInMs);
313 return false;
314 }
315
316 if (m_gotResults)
317 return true;
318
319 QCoreApplication::processEvents();
320 QThread::msleep(20);
321 }
322 }
323
run()324 bool OpenDocumentCommand::run()
325 {
326 qCDebug(debug) << "line" << context().lineNumber << "OpenDocumentCommand" << m_documentFilePath;
327
328 const bool openEditorSucceeded = Core::EditorManager::openEditor(m_documentFilePath);
329 QTC_ASSERT(openEditorSucceeded, return false);
330
331 auto *processor = ClangEditorDocumentProcessor::get(m_documentFilePath);
332 QTC_ASSERT(processor, return false);
333
334 WaitForUpdatedCodeWarnings waiter(processor);
335 return waiter.wait(timeOutInMs());
336 }
337
parse(BatchFileLineTokenizer & arguments,const CommandContext & context)338 Command::Ptr OpenDocumentCommand::parse(BatchFileLineTokenizer &arguments,
339 const CommandContext &context)
340 {
341 const QString documentFilePath = arguments.nextToken();
342 if (documentFilePath.isEmpty()) {
343 qWarning("%s:%d: error: No document file path given.",
344 qPrintable(context.filePath),
345 context.lineNumber);
346 return Command::Ptr();
347 }
348
349 const QString absoluteDocumentFilePath = QFileInfo(documentFilePath).absoluteFilePath();
350
351 return Command::Ptr(new OpenDocumentCommand(context, absoluteDocumentFilePath));
352 }
353
354 class CloseAllDocuments : public Command
355 {
356 public:
357 CloseAllDocuments(const CommandContext &context);
358
359 bool run() override;
360
361 static Command::Ptr parse(BatchFileLineTokenizer &arguments, const CommandContext &context);
362 };
363
CloseAllDocuments(const CommandContext & context)364 CloseAllDocuments::CloseAllDocuments(const CommandContext &context)
365 : Command(context)
366 {
367 }
368
run()369 bool CloseAllDocuments::run()
370 {
371 qCDebug(debug) << "line" << context().lineNumber << "CloseAllDocuments";
372
373 return Core::EditorManager::closeAllEditors(/*askAboutModifiedEditors=*/ false);
374 }
375
parse(BatchFileLineTokenizer & arguments,const CommandContext & context)376 Command::Ptr CloseAllDocuments::parse(BatchFileLineTokenizer &arguments,
377 const CommandContext &context)
378 {
379 const QString argument = arguments.nextToken();
380 if (!argument.isEmpty()) {
381 qWarning("%s:%d: error: Unexpected argument.",
382 qPrintable(context.filePath),
383 context.lineNumber);
384 return Command::Ptr();
385 }
386
387 return Command::Ptr(new CloseAllDocuments(context));
388 }
389
390 class InsertTextCommand : public Command
391 {
392 public:
393 // line and column are 1-based
394 InsertTextCommand(const CommandContext &context, const QString &text);
395
396 bool run() override;
397
398 static Command::Ptr parse(BatchFileLineTokenizer &arguments,
399 const CommandContext &context);
400
401 private:
402 const QString m_textToInsert;
403 };
404
InsertTextCommand(const CommandContext & context,const QString & text)405 InsertTextCommand::InsertTextCommand(const CommandContext &context, const QString &text)
406 : Command(context)
407 , m_textToInsert(text)
408 {
409 }
410
currentTextEditor()411 TextEditor::BaseTextEditor *currentTextEditor()
412 {
413 return qobject_cast<TextEditor::BaseTextEditor*>(Core::EditorManager::currentEditor());
414 }
415
run()416 bool InsertTextCommand::run()
417 {
418 qCDebug(debug) << "line" << context().lineNumber << "InsertTextCommand" << m_textToInsert;
419
420 TextEditor::BaseTextEditor *editor = currentTextEditor();
421 QTC_ASSERT(editor, return false);
422 const QString documentFilePath = editor->document()->filePath().toString();
423 auto *processor = ClangEditorDocumentProcessor::get(documentFilePath);
424 QTC_ASSERT(processor, return false);
425
426 editor->insert(m_textToInsert);
427
428 WaitForUpdatedCodeWarnings waiter(processor);
429 return waiter.wait(timeOutInMs());
430 }
431
parse(BatchFileLineTokenizer & arguments,const CommandContext & context)432 Command::Ptr InsertTextCommand::parse(BatchFileLineTokenizer &arguments,
433 const CommandContext &context)
434 {
435 const QString textToInsert = arguments.nextToken();
436 if (textToInsert.isEmpty()) {
437 qWarning("%s:%d: error: No text to insert given.",
438 qPrintable(context.filePath),
439 context.lineNumber);
440 return Command::Ptr();
441 }
442
443 return Command::Ptr(new InsertTextCommand(context, textToInsert));
444 }
445
446 class CompleteCommand : public Command
447 {
448 public:
449 CompleteCommand(const CommandContext &context);
450
451 bool run() override;
452
453 static Command::Ptr parse(BatchFileLineTokenizer &arguments,
454 const CommandContext &context);
455 };
456
CompleteCommand(const CommandContext & context)457 CompleteCommand::CompleteCommand(const CommandContext &context)
458 : Command(context)
459 {
460 }
461
run()462 bool CompleteCommand::run()
463 {
464 qCDebug(debug) << "line" << context().lineNumber << "CompleteCommand";
465
466 TextEditor::BaseTextEditor *editor = currentTextEditor();
467 QTC_ASSERT(editor, return false);
468
469 const QString documentFilePath = editor->document()->filePath().toString();
470 auto *processor = ClangEditorDocumentProcessor::get(documentFilePath);
471 QTC_ASSERT(processor, return false);
472
473 return !completionResults(editor, QStringList(), timeOutInMs()).isNull();
474 }
475
parse(BatchFileLineTokenizer & arguments,const CommandContext & context)476 Command::Ptr CompleteCommand::parse(BatchFileLineTokenizer &arguments,
477 const CommandContext &context)
478 {
479 Q_UNUSED(arguments)
480 Q_UNUSED(context)
481
482 return Command::Ptr(new CompleteCommand(context));
483 }
484
485 class SetCursorCommand : public Command
486 {
487 public:
488 // line and column are 1-based
489 SetCursorCommand(const CommandContext &context, int line, int column);
490
491 bool run() override;
492
493 static Command::Ptr parse(BatchFileLineTokenizer &arguments,
494 const CommandContext &context);
495
496 private:
497 int m_line;
498 int m_column;
499 };
500
SetCursorCommand(const CommandContext & context,int line,int column)501 SetCursorCommand::SetCursorCommand(const CommandContext &context, int line, int column)
502 : Command(context)
503 , m_line(line)
504 , m_column(column)
505 {
506 }
507
run()508 bool SetCursorCommand::run()
509 {
510 qCDebug(debug) << "line" << context().lineNumber << "SetCursorCommand" << m_line << m_column;
511
512 TextEditor::BaseTextEditor *editor = currentTextEditor();
513 QTC_ASSERT(editor, return false);
514
515 editor->gotoLine(m_line, m_column - 1);
516
517 return true;
518 }
519
parse(BatchFileLineTokenizer & arguments,const CommandContext & context)520 Command::Ptr SetCursorCommand::parse(BatchFileLineTokenizer &arguments,
521 const CommandContext &context)
522 {
523 // Process line
524 const QString line = arguments.nextToken();
525 if (line.isEmpty()) {
526 qWarning("%s:%d: error: No line number given.",
527 qPrintable(context.filePath),
528 context.lineNumber);
529 return Command::Ptr();
530 }
531 bool converted = false;
532 const int lineNumber = line.toInt(&converted);
533 if (!converted) {
534 qWarning("%s:%d: error: Invalid line number.",
535 qPrintable(context.filePath),
536 context.lineNumber);
537 return Command::Ptr();
538 }
539
540 // Process column
541 const QString column = arguments.nextToken();
542 if (column.isEmpty()) {
543 qWarning("%s:%d: error: No column number given.",
544 qPrintable(context.filePath),
545 context.lineNumber);
546 return Command::Ptr();
547 }
548 converted = false;
549 const int columnNumber = column.toInt(&converted);
550 if (!converted) {
551 qWarning("%s:%d: error: Invalid column number.",
552 qPrintable(context.filePath),
553 context.lineNumber);
554 return Command::Ptr();
555 }
556
557 return Command::Ptr(new SetCursorCommand(context, lineNumber, columnNumber));
558 }
559
560 class ProcessEventsCommand : public Command
561 {
562 public:
563 ProcessEventsCommand(const CommandContext &context, int durationInMs);
564
565 bool run() override;
566
567 static Command::Ptr parse(BatchFileLineTokenizer &arguments,
568 const CommandContext &context);
569
570 private:
571 int m_durationInMs;
572 };
573
ProcessEventsCommand(const CommandContext & context,int durationInMs)574 ProcessEventsCommand::ProcessEventsCommand(const CommandContext &context,
575 int durationInMs)
576 : Command(context)
577 , m_durationInMs(durationInMs)
578 {
579 }
580
run()581 bool ProcessEventsCommand::run()
582 {
583 qCDebug(debug) << "line" << context().lineNumber << "ProcessEventsCommand" << m_durationInMs;
584
585 QElapsedTimer time;
586 time.start();
587
588 forever {
589 if (time.elapsed() > m_durationInMs)
590 return true;
591
592 QCoreApplication::processEvents();
593 QThread::msleep(20);
594 }
595 }
596
parse(BatchFileLineTokenizer & arguments,const CommandContext & context)597 Command::Ptr ProcessEventsCommand::parse(BatchFileLineTokenizer &arguments,
598 const CommandContext &context)
599 {
600 const QString durationInMsText = arguments.nextToken();
601 if (durationInMsText.isEmpty()) {
602 qWarning("%s:%d: error: No duration given.",
603 qPrintable(context.filePath),
604 context.lineNumber);
605 return Command::Ptr();
606 }
607
608 bool converted = false;
609 const int durationInMs = durationInMsText.toInt(&converted);
610 if (!converted) {
611 qWarning("%s:%d: error: Invalid duration given.",
612 qPrintable(context.filePath),
613 context.lineNumber);
614 return Command::Ptr();
615 }
616
617 return Command::Ptr(new ProcessEventsCommand(context, durationInMs));
618 }
619
620 class BatchFileReader
621 {
622 public:
623 BatchFileReader(const QString &filePath);
624
625 bool isFilePathValid() const;
626
627 QString read() const;
628
629 private:
630 const QString m_batchFilePath;
631 };
632
BatchFileReader(const QString & filePath)633 BatchFileReader::BatchFileReader(const QString &filePath)
634 : m_batchFilePath(filePath)
635 {
636 }
637
isFilePathValid() const638 bool BatchFileReader::isFilePathValid() const
639 {
640 QFileInfo fileInfo(m_batchFilePath);
641
642 return !m_batchFilePath.isEmpty()
643 && fileInfo.isFile()
644 && fileInfo.isReadable();
645 }
646
read() const647 QString BatchFileReader::read() const
648 {
649 QFile file(m_batchFilePath);
650 QTC_CHECK(file.open(QFile::ReadOnly | QFile::Text));
651
652 return QString::fromLocal8Bit(file.readAll());
653 }
654
655 class BatchFileParser
656 {
657 public:
658 BatchFileParser(const QString &filePath,
659 const QString &commands);
660
661 bool parse();
662 QVector<Command::Ptr> commands() const;
663
664 private:
665 bool advanceLine();
666 QString currentLine() const;
667 bool parseLine(const QString &line);
668
669 private:
670 using ParseFunction = Command::Ptr (*)(BatchFileLineTokenizer &, const CommandContext &);
671 using CommandToParseFunction = QHash<QString, ParseFunction>;
672 CommandToParseFunction m_commandParsers;
673
674 int m_currentLineIndex = -1;
675 CommandContext m_context;
676 QStringList m_lines;
677 QVector<Command::Ptr> m_commands;
678 };
679
BatchFileParser(const QString & filePath,const QString & commands)680 BatchFileParser::BatchFileParser(const QString &filePath,
681 const QString &commands)
682 : m_lines(commands.split('\n'))
683 {
684 m_context.filePath = filePath;
685
686 m_commandParsers.insert("openProject", &OpenProjectCommand::parse);
687 m_commandParsers.insert("openDocument", &OpenDocumentCommand::parse);
688 m_commandParsers.insert("closeAllDocuments", &CloseAllDocuments::parse);
689 m_commandParsers.insert("setCursor", &SetCursorCommand::parse);
690 m_commandParsers.insert("insertText", &InsertTextCommand::parse);
691 m_commandParsers.insert("complete", &CompleteCommand::parse);
692 m_commandParsers.insert("processEvents", &ProcessEventsCommand::parse);
693 }
694
parse()695 bool BatchFileParser::parse()
696 {
697 while (advanceLine()) {
698 const QString line = currentLine().trimmed();
699 if (line.isEmpty() || line.startsWith('#'))
700 continue;
701
702 if (!parseLine(line))
703 return false;
704 }
705
706 return true;
707 }
708
commands() const709 QVector<Command::Ptr> BatchFileParser::commands() const
710 {
711 return m_commands;
712 }
713
advanceLine()714 bool BatchFileParser::advanceLine()
715 {
716 ++m_currentLineIndex;
717 m_context.lineNumber = m_currentLineIndex + 1;
718 return m_currentLineIndex < m_lines.size();
719 }
720
currentLine() const721 QString BatchFileParser::currentLine() const
722 {
723 return m_lines[m_currentLineIndex];
724 }
725
parseLine(const QString & line)726 bool BatchFileParser::parseLine(const QString &line)
727 {
728 BatchFileLineTokenizer tokenizer(line);
729 QString command = tokenizer.nextToken();
730 QTC_CHECK(!command.isEmpty());
731
732 if (const ParseFunction parseFunction = m_commandParsers.value(command)) {
733 if (Command::Ptr cmd = parseFunction(tokenizer, m_context)) {
734 m_commands.append(cmd);
735 return true;
736 }
737
738 return false;
739 }
740
741 qWarning("%s:%d: error: Unknown command \"%s\".",
742 qPrintable(m_context.filePath),
743 m_context.lineNumber,
744 qPrintable(command));
745
746 return false;
747 }
748
749 } // anonymous namespace
750
applySubstitutions(const QString & filePath,const QString & text)751 static QString applySubstitutions(const QString &filePath, const QString &text)
752 {
753 const QString dirPath = QFileInfo(filePath).absolutePath();
754
755 QString result = text;
756 result.replace("${PWD}", dirPath);
757
758 return result;
759 }
760
runClangBatchFile(const QString & filePath)761 bool runClangBatchFile(const QString &filePath)
762 {
763 qWarning("ClangBatchFileProcessor: Running \"%s\".", qPrintable(filePath));
764
765 BatchFileReader reader(filePath);
766 QTC_ASSERT(reader.isFilePathValid(), return false);
767 const QString fileContent = reader.read();
768 const QString fileContentWithSubstitutionsApplied = applySubstitutions(filePath, fileContent);
769
770 BatchFileParser parser(filePath, fileContentWithSubstitutionsApplied);
771 QTC_ASSERT(parser.parse(), return false);
772 const QVector<Command::Ptr> commands = parser.commands();
773
774 Utils::ExecuteOnDestruction closeAllEditors([](){
775 qWarning("ClangBatchFileProcessor: Finished, closing all documents.");
776 QTC_CHECK(Core::EditorManager::closeAllEditors(/*askAboutModifiedEditors=*/ false));
777 });
778
779 foreach (const Command::Ptr &command, commands) {
780 const bool runSucceeded = command->run();
781 QCoreApplication::processEvents(); // Update GUI
782
783 if (!runSucceeded) {
784 const CommandContext context = command->context();
785 qWarning("%s:%d: Failed to run.",
786 qPrintable(context.filePath),
787 context.lineNumber);
788 return false;
789 }
790 }
791
792 return true;
793 }
794
795 } // namespace Internal
796 } // namespace ClangCodeModel
797
798 #include "clangbatchfileprocessor.moc"
799