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