diff --git a/src/plugins/projectexplorer/gnumakeparser.cpp b/src/plugins/projectexplorer/gnumakeparser.cpp index 929a99fa5aafc05bfdba335bddcb2e27afbdaa5e..1027b12374546dfcf7b2db9b0df4790326b697d6 100644 --- a/src/plugins/projectexplorer/gnumakeparser.cpp +++ b/src/plugins/projectexplorer/gnumakeparser.cpp @@ -28,6 +28,8 @@ **************************************************************************/ #include "gnumakeparser.h" + +#include "projectexplorerconstants.h" #include "taskwindow.h" #include <QtCore/QDir> @@ -35,11 +37,17 @@ using namespace ProjectExplorer; +namespace { + const char * const MAKE_PATTERN("^(mingw(32|64)-|g)?make(\\[\\d+\\])?:\\s"); +} + GnuMakeParser::GnuMakeParser(const QString &dir) { - //make[4]: Entering directory `/some/dir' - m_makeDir.setPattern("^(?:mingw32-)?make.*: (\\w+) directory .(.+).$"); + m_makeDir.setPattern(QLatin1String(MAKE_PATTERN) + + QLatin1String("(\\w+) directory .(.+).$")); m_makeDir.setMinimal(true); + m_makeLine.setPattern(QLatin1String(MAKE_PATTERN) + QLatin1String("(.*)$")); + m_makeLine.setMinimal(true); addDirectory(dir); } @@ -48,10 +56,24 @@ void GnuMakeParser::stdOutput(const QString &line) QString lne = line.trimmed(); if (m_makeDir.indexIn(lne) > -1) { - if (m_makeDir.cap(1) == "Leaving") - removeDirectory(m_makeDir.cap(2)); + if (m_makeDir.cap(4) == "Leaving") + removeDirectory(m_makeDir.cap(5)); else - addDirectory(m_makeDir.cap(2)); + addDirectory(m_makeDir.cap(5)); + return; + } + if (m_makeLine.indexIn(lne) > -1) { + QString message = m_makeLine.cap(4); + Task task(Task::Warning, + message, + QString() /* filename */, + -1, /* line */ + Constants::TASK_CATEGORY_BUILDSYSTEM); + if (message.startsWith(QLatin1String("*** "))) { + task.description = task.description.mid(4); + task.type = Task::Error; + } + addTask(task); return; } @@ -70,9 +92,9 @@ void GnuMakeParser::removeDirectory(const QString &dir) m_directories.removeAll(dir); } -void GnuMakeParser::taskAdded(const ProjectExplorer::Task &task) +void GnuMakeParser::taskAdded(const Task &task) { - ProjectExplorer::Task editable(task); + Task editable(task); QString filePath(QDir::cleanPath(task.file.trimmed())); if (!filePath.isEmpty() && !QDir::isAbsolutePath(filePath)) { @@ -93,3 +115,237 @@ void GnuMakeParser::taskAdded(const ProjectExplorer::Task &task) IOutputParser::taskAdded(editable); } + +#if defined WITH_TESTS +QStringList GnuMakeParser::searchDirectories() const +{ + return m_directories; +} +#endif + +// Unit tests: + +#ifdef WITH_TESTS +# include <QTest> + +# include <QtCore/QDebug> +# include <QtCore/QUuid> + +# include "outputparser_test.h" +# include "projectexplorer.h" +# include "projectexplorerconstants.h" + +# include "metatypedeclarations.h" + +void ProjectExplorerPlugin::testGnuMakeParserParsing_data() +{ + QTest::addColumn<QStringList>("extraSearchDirs"); + QTest::addColumn<QString>("input"); + QTest::addColumn<OutputParserTester::Channel>("inputChannel"); + QTest::addColumn<QString>("childStdOutLines"); + QTest::addColumn<QString>("childStdErrLines"); + QTest::addColumn<QList<Task> >("tasks"); + QTest::addColumn<QString>("outputLines"); + QTest::addColumn<QStringList>("additionalSearchDirs"); + + QTest::newRow("pass-through stdout") + << QStringList() + << QString::fromLatin1("Sometext") << OutputParserTester::STDOUT + << QString::fromLatin1("Sometext") << QString() + << QList<Task>() + << QString() + << QStringList(); + QTest::newRow("pass-through stderr") + << QStringList() + << QString::fromLatin1("Sometext") << OutputParserTester::STDERR + << QString() << QString::fromLatin1("Sometext") + << QList<Task>() + << QString() + << QStringList(); + // make sure adding directories works (once;-) + QTest::newRow("entering directory") + << (QStringList() << QString::fromLatin1("/test/dir") ) + << QString::fromLatin1("make[4]: Entering directory `/home/code/build/qt/examples/opengl/grabber'\n" + "make[4]: Entering directory `/home/code/build/qt/examples/opengl/grabber'\n") + << OutputParserTester::STDOUT + << QString() << QString() + << QList<Task>() + << QString() + << (QStringList() << QString::fromLatin1("/home/code/build/qt/examples/opengl/grabber") << QString::fromLatin1("/test/dir")); + QTest::newRow("leaving directory") + << (QStringList() << QString::fromLatin1("/home/code/build/qt/examples/opengl/grabber") << QString::fromLatin1("/test/dir")) + << QString::fromLatin1("make[4]: Leaving directory `/home/code/build/qt/examples/opengl/grabber'") + << OutputParserTester::STDOUT + << QString() << QString() + << QList<Task>() + << QString() + << (QStringList() << QString::fromLatin1("/test/dir")); + QTest::newRow("make error") + << QStringList() + << QString::fromLatin1("make: *** No rule to make target `hello.c', needed by `hello.o'. Stop.") + << OutputParserTester::STDOUT + << QString() << QString() + << (QList<Task>() + << Task(Task::Error, + QString::fromLatin1("No rule to make target `hello.c', needed by `hello.o'. Stop."), + QString(), -1, + Constants::TASK_CATEGORY_BUILDSYSTEM)) + << QString() + << QStringList(); + QTest::newRow("make warning") + << QStringList() + << QString::fromLatin1("make: warning: ignoring old commands for target `xxx'") + << OutputParserTester::STDOUT + << QString() << QString() + << (QList<Task>() + << Task(Task::Warning, + QString::fromLatin1("warning: ignoring old commands for target `xxx'"), + QString(), -1, + Constants::TASK_CATEGORY_BUILDSYSTEM)) + << QString() + << QStringList(); +} + +void ProjectExplorerPlugin::testGnuMakeParserParsing() +{ + OutputParserTester testbench; + GnuMakeParser *childParser = new GnuMakeParser; + testbench.appendOutputParser(childParser); + QFETCH(QStringList, extraSearchDirs); + QFETCH(QString, input); + QFETCH(OutputParserTester::Channel, inputChannel); + QFETCH(QList<Task>, tasks); + QFETCH(QString, childStdOutLines); + QFETCH(QString, childStdErrLines); + QFETCH(QString, outputLines); + QFETCH(QStringList, additionalSearchDirs); + + QStringList searchDirs = childParser->searchDirectories(); + + // add extra directories: + foreach(const QString &dir, extraSearchDirs) + childParser->addDirectory(dir); + + testbench.testParsing(input, inputChannel, + tasks, childStdOutLines, childStdErrLines, + outputLines); + + // make sure we still have all the original dirs + QStringList newSearchDirs = childParser->searchDirectories(); + foreach (const QString &dir, searchDirs) { + QVERIFY(newSearchDirs.contains(dir)); + newSearchDirs.removeOne(dir); + } + + // make sure we have all additional dirs: + foreach (const QString &dir, additionalSearchDirs) { + QVERIFY(newSearchDirs.contains(dir)); + newSearchDirs.removeOne(dir); + } + // make sure we have no extra cruft: + QVERIFY(newSearchDirs.isEmpty()); +} + +void ProjectExplorerPlugin::testGnuMakeParserTaskMangling_data() +{ + QTest::addColumn<QStringList>("files"); + QTest::addColumn<QStringList>("searchDirectories"); + QTest::addColumn<Task>("inputTask"); + QTest::addColumn<Task>("outputTask"); + + QTest::newRow("no filename") + << QStringList() + << QStringList() + << Task(Task::Error, + QLatin1String("no filename, no mangling"), + QString(), + -1, + Constants::TASK_CATEGORY_COMPILE) + << Task(Task::Error, + QLatin1String("no filename, no mangling"), + QString(), + -1, + Constants::TASK_CATEGORY_COMPILE); + QTest::newRow("no mangling") + << QStringList() + << QStringList() + << Task(Task::Error, + QLatin1String("unknown filename, no mangling"), + QString::fromLatin1("some/path/unknown.cpp"), + -1, + Constants::TASK_CATEGORY_COMPILE) + << Task(Task::Error, + QLatin1String("unknown filename, no mangling"), + QString::fromLatin1("some/path/unknown.cpp"), + -1, + Constants::TASK_CATEGORY_COMPILE); + QTest::newRow("find file") + << (QStringList() << "test/file.cpp") + << (QStringList() << "test") + << Task(Task::Error, + QLatin1String("mangling"), + QString::fromLatin1("file.cpp"), + 10, + Constants::TASK_CATEGORY_COMPILE) + << Task(Task::Error, + QLatin1String("mangling"), + QString::fromLatin1("$TMPDIR/test/file.cpp"), + 10, + Constants::TASK_CATEGORY_COMPILE); +} + +void ProjectExplorerPlugin::testGnuMakeParserTaskMangling() +{ + OutputParserTester testbench; + GnuMakeParser *childParser = new GnuMakeParser; + testbench.appendOutputParser(childParser); + + QFETCH(QStringList, files); + QFETCH(QStringList, searchDirectories); + QFETCH(Task, inputTask); + QFETCH(Task, outputTask); + + // setup files: + QString tempdir; +#if defined Q_OS_WIN + tempdir = QDir::fromNativeSeparators(qgetenv("TEMP")); +#else + tempdir = QLatin1String("/tmp"); +#endif + tempdir.append(QChar('/')); + tempdir.append(QUuid::createUuid().toString()); + tempdir.append(QChar('/')); + + QDir filedir(tempdir); + foreach (const QString &file, files) { + Q_ASSERT(!file.startsWith(QChar('/'))); + Q_ASSERT(!file.contains(QLatin1String("../"))); + + filedir.mkpath(file.left(file.lastIndexOf(QChar('/')))); + + QFile tempfile(tempdir + file); + if (!tempfile.open(QIODevice::WriteOnly)) + continue; + tempfile.write("Delete me again!"); + tempfile.close(); + } + + // setup search dirs: + foreach (const QString &dir, searchDirectories) { + Q_ASSERT(!dir.startsWith(QChar('/'))); + Q_ASSERT(!dir.contains(QLatin1String("../"))); + childParser->addDirectory(tempdir + dir); + } + + // fix up output task file: + if (outputTask.file.startsWith(QLatin1String("$TMPDIR/"))) + outputTask.file = outputTask.file.replace(QLatin1String("$TMPDIR/"), tempdir); + + // test mangling: + testbench.testTaskMangling(inputTask, outputTask); + + // clean up: + foreach (const QString &file, files) + filedir.rmpath(tempdir + file); +} +#endif diff --git a/src/plugins/projectexplorer/gnumakeparser.h b/src/plugins/projectexplorer/gnumakeparser.h index 30f82bca8c8bb695f1c6e873115b995f96fd9404..433375622310a19f466fa4d47a44e755f1aee0bf 100644 --- a/src/plugins/projectexplorer/gnumakeparser.h +++ b/src/plugins/projectexplorer/gnumakeparser.h @@ -46,6 +46,8 @@ public: virtual void stdOutput(const QString &line); + QStringList searchDirectories() const; + public slots: virtual void taskAdded(const ProjectExplorer::Task &task); @@ -54,7 +56,13 @@ private: void removeDirectory(const QString &dir); QRegExp m_makeDir; + QRegExp m_makeLine; + QStringList m_directories; + +#if defined WITH_TESTS + friend class ProjectExplorerPlugin; +#endif }; } // namespace ProjectExplorer diff --git a/src/plugins/projectexplorer/projectexplorer.h b/src/plugins/projectexplorer/projectexplorer.h index 7c13947df347622f81bfcc057cd147d10821b733..440314247419d0c29476d2cb5c03705f91ab98eb 100644 --- a/src/plugins/projectexplorer/projectexplorer.h +++ b/src/plugins/projectexplorer/projectexplorer.h @@ -223,6 +223,11 @@ private slots: #ifdef WITH_TESTS void testGccOutputParsers_data(); void testGccOutputParsers(); + + void testGnuMakeParserParsing_data(); + void testGnuMakeParserParsing(); + void testGnuMakeParserTaskMangling_data(); + void testGnuMakeParserTaskMangling(); #endif private: