Skip to content
Snippets Groups Projects
sbsv2parser.cpp 9.04 KiB
Newer Older
/**************************************************************************
**
** This file is part of Qt Creator
**
** Copyright (c) 2010 Nokia Corporation and/or its subsidiary(-ies).
**
** Contact: Nokia Corporation (qt-info@nokia.com)
**
** Commercial Usage
**
** Licensees holding valid Qt Commercial licenses may use this file in
** accordance with the Qt Commercial License Agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and Nokia.
**
** GNU Lesser General Public License Usage
**
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL included in the
** packaging of this file.  Please review the following information to
** ensure the GNU Lesser General Public License version 2.1 requirements
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** If you are unsure which license is appropriate for your use, please
** contact the sales department at http://qt.nokia.com/contact.
**
**************************************************************************/

#include "sbsv2parser.h"

#include <extensionsystem/pluginmanager.h>
#include <projectexplorer/gnumakeparser.h>
#include <projectexplorer/projectexplorerconstants.h>
#include <projectexplorer/taskhub.h>
#include <projectexplorer/taskwindow.h>

#include <QtCore/QFile>
#include <QtCore/QFileInfo>

using namespace Qt4ProjectManager;
using namespace ProjectExplorer;
using namespace ProjectExplorer::Constants;

/*
 * This parser takes a somewhat unusal approach of just eating most of its
 * input :-)
 *
 * Only when the XML-based log file generated by SBSv2 is announced it will
 * open that file and parse that. Tasks will then get generated by passing
 * any CDATA found in the XML file on to its child parsers (using STDERR).
 *
 * In additon <error> and <warning> tags are reported, too.
 */

SbsV2Parser::SbsV2Parser() :
    m_hub(0)
{
    setObjectName(QLatin1String("SbsV2Parser"));
    ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
    m_hub = pm->getObject<TaskHub>();
}

void SbsV2Parser::stdOutput(const QString &line)
{
    // Eat most output!
    if (line.startsWith(QLatin1String("sbs: build log in "))) {
        QString logfile = line.mid(18).trimmed();
        parseLogFile(logfile);
        addTask(ProjectExplorer::Task(Task::Unknown, tr("SBSv2 build log"),
                                      logfile, -1,
                                      QLatin1String(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM)));
    }
}

void SbsV2Parser::stdError(const QString &line)
{
    // Eat all output!
    Q_UNUSED(line);
}

void SbsV2Parser::taskAdded(const ProjectExplorer::Task &task)
{
    // Fix pathes:
    ProjectExplorer::Task tmp(task);

    if (!tmp.file.isEmpty()) {
        QFileInfo fi(tmp.file);
        if (!fi.isAbsolute()) {
            if (m_currentSource.exists(tmp.file))
                tmp.file = m_currentSource.absoluteFilePath(tmp.file);
            else if (m_currentTarget.exists(tmp.file))
                tmp.file = m_currentTarget.absoluteFilePath(tmp.file);
        }
    }

    // Do not report tasks from our children via the normal channel:
    // We do not want them get registered with the Compile output window!
    m_hub->addTask(tmp);
}

void SbsV2Parser::parseLogFile(const QString &file)
{
    QFile logFile(file);
    logFile.open(QIODevice::ReadOnly);
    m_log.setDevice(&logFile);

    if (m_log.readNextStartElement()) {
         if (m_log.name() == QLatin1String("buildlog"))
             readBuildLog();
         else
Friedemann Kleint's avatar
Friedemann Kleint committed
             m_log.raiseError(tr("The file '%1' is not a SBSv2 log file.").arg(file));
     }
}

void SbsV2Parser::readBuildLog()
{
    Q_ASSERT(m_log.isStartElement() && m_log.name() == QLatin1String("buildlog"));

     while (m_log.readNextStartElement()) {
         if (m_log.name() == QLatin1String("error"))
             readError();
         else if (m_log.name() == QLatin1String("warning"))
             readWarning();
         else if (m_log.name() == QLatin1String("recipe"))
             readRecipe();
         else
             m_log.skipCurrentElement();
     }
}

void SbsV2Parser::readError()
{
    Q_ASSERT(m_log.isStartElement() && m_log.name() == QLatin1String("error"));

    QString error = m_log.readElementText();
    addTask(Task(Task::Error, error, QString(), -1, Constants::TASK_CATEGORY_BUILDSYSTEM));
}

void SbsV2Parser::readWarning()
{
    Q_ASSERT(m_log.isStartElement() && m_log.name() == QLatin1String("warning"));

    QString warning = m_log.readElementText();
    addTask(Task(Task::Warning, warning, QString(), -1, Constants::TASK_CATEGORY_BUILDSYSTEM));
}

void SbsV2Parser::readRecipe()
{
    Q_ASSERT(m_log.isStartElement() && m_log.name() == QLatin1String("recipe"));
    const QString name = m_log.attributes().value(QLatin1String("name")).toString();
Tobias Hunger's avatar
Tobias Hunger committed
    m_currentSource = QDir(m_log.attributes().value("source").toString()).absolutePath();
    m_currentTarget = QDir(m_log.attributes().value("target").toString()).absolutePath();

    int returnCode = 0;
    QString outputText;
    QXmlStreamReader::TokenType tokenType = QXmlStreamReader::Invalid;
    while ((tokenType = m_log.readNext()) != QXmlStreamReader::Invalid) {
        if (tokenType == QXmlStreamReader::Characters) {
            outputText.append(m_log.text());
        } else if (tokenType == QXmlStreamReader::StartElement) {
            if (m_log.name() == QLatin1String("status")) {
                if (m_log.attributes().value(QLatin1String("exit")) == QLatin1String("failed"))
                    returnCode = m_log.attributes().value(QLatin1String("code")).toString().toInt();
            }
        } else if (tokenType == QXmlStreamReader::EndElement) {
            if (m_log.name() == QLatin1String("recipe"))
                break;
        }
    }
Tobias Hunger's avatar
Tobias Hunger committed
    QStringList output = outputText.split(QChar('\n'));
    outputText.clear();
    foreach (const QString &line, output) {
        if (line.isEmpty())
            continue;
        if (line.startsWith(QChar('+'))) {
            outputText.append(tr("Running command: %1\n").arg(line.mid(2)));
            continue;
Tobias Hunger's avatar
Tobias Hunger committed
        outputText.append(line);
        outputText.append(QChar('\n'));
        if (name == QLatin1String("compile") || name == QLatin1String("qmake_extra_pre_targetdep"))
            IOutputParser::stdError(line);
    }

    if (returnCode != 0) {
        //: %1 is the SBSv2 build recipe name, %2 the return code of the failed command
        QString description = tr("Recipe %1 failed with exit code %2.").arg(name).arg(returnCode);
        m_hub->addTask(Task(Task::Error, description, QString(), -1,
                            ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM));
        m_hub->addTask(Task(Task::Unknown, outputText, QString(), -1,
                            ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM));
    }
}

// Unit tests:

#ifdef WITH_TESTS
#   include <QTest>

#   include "qt4projectmanagerplugin.h"

#   include "projectexplorer/outputparser_test.h"

using namespace Qt4ProjectManager::Internal;

void Qt4ProjectManagerPlugin::testSbsV2OutputParsers_data()
{
    QTest::addColumn<QString>("input");
    QTest::addColumn<OutputParserTester::Channel>("inputChannel");
    QTest::addColumn<QString>("childStdOutLines");
    QTest::addColumn<QString>("childStdErrLines");
    QTest::addColumn<QList<ProjectExplorer::Task> >("tasks");
    QTest::addColumn<QString>("outputLines");


    QTest::newRow("eat stdout")
            << QString::fromLatin1("   Sometext") << OutputParserTester::STDOUT
            << QString() << QString()
            << QList<ProjectExplorer::Task>()
            << QString();
    QTest::newRow("eat stderr")
            << QString::fromLatin1("   Sometext") << OutputParserTester::STDERR
            << QString() << QString()
            << QList<ProjectExplorer::Task>()
            << QString();

    QTest::newRow("build log")
            << QString::fromLatin1("sbs: build log in X:/epoc32/build/Makefile.2010-08-10-15-25-52.log") << OutputParserTester::STDOUT
            << QString() << QString()
            << (QList<ProjectExplorer::Task>()
                    << ProjectExplorer::Task(Task::Unknown, QLatin1String("SBSv2 build log"),
                                             QLatin1String("X:/epoc32/build/Makefile.2010-08-10-15-25-52.log"), -1,
                                             QLatin1String(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM)))
            << QString();
}

void Qt4ProjectManagerPlugin::testSbsV2OutputParsers()
{
    OutputParserTester testbench;
    testbench.appendOutputParser(new SbsV2Parser);
    QFETCH(QString, input);
    QFETCH(OutputParserTester::Channel, inputChannel);
    QFETCH(QList<Task>, tasks);
    QFETCH(QString, childStdOutLines);
    QFETCH(QString, childStdErrLines);
    QFETCH(QString, outputLines);

    testbench.testParsing(input, inputChannel,
                          tasks, childStdOutLines, childStdErrLines,
                          outputLines);
}
#endif