/**************************************************************************** ** ** Copyright (C) Filippo Cucchetto ** Contact: http://www.qt.io/licensing ** ** This file is part of Qt Creator. ** ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 3 as published by the Free Software ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-3.0.html. ** ****************************************************************************/ #include "nimcompilerbuildstep.h" #include "nimbuildconfiguration.h" #include "nimbuildsystem.h" #include "nimconstants.h" #include "nimtoolchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace Nim { // NimParser class NimParser : public ProjectExplorer::OutputTaskParser { Result handleLine(const QString &lne, Utils::OutputFormat) override { const QString line = lne.trimmed(); static const QRegularExpression regex("(.+.nim)\\((\\d+), (\\d+)\\) (.+)"); static const QRegularExpression warning("(Warning):(.*)"); static const QRegularExpression error("(Error):(.*)"); const QRegularExpressionMatch match = regex.match(line); if (!match.hasMatch()) return Status::NotHandled; const QString filename = match.captured(1); bool lineOk = false; const int lineNumber = match.captured(2).toInt(&lineOk); const QString message = match.captured(4); if (!lineOk) return Status::NotHandled; Task::TaskType type = Task::Unknown; if (warning.match(message).hasMatch()) type = Task::Warning; else if (error.match(message).hasMatch()) type = Task::Error; else return Status::NotHandled; const CompileTask t(type, message, absoluteFilePath(FilePath::fromUserInput(filename)), lineNumber); LinkSpecs linkSpecs; addLinkSpecForAbsoluteFilePath(linkSpecs, t.file, t.line, match, 1); scheduleTask(t, 1); return {Status::Done, linkSpecs}; } }; NimCompilerBuildStep::NimCompilerBuildStep(BuildStepList *parentList, Utils::Id id) : AbstractProcessStep(parentList, id) { setDefaultDisplayName(tr(Constants::C_NIMCOMPILERBUILDSTEP_DISPLAY)); setDisplayName(tr(Constants::C_NIMCOMPILERBUILDSTEP_DISPLAY)); setCommandLineProvider([this] { return commandLine(); }); connect(project(), &ProjectExplorer::Project::fileListChanged, this, &NimCompilerBuildStep::updateTargetNimFile); } void NimCompilerBuildStep::setupOutputFormatter(OutputFormatter *formatter) { formatter->addLineParser(new NimParser); formatter->addLineParsers(target()->kit()->createOutputParsers()); formatter->addSearchDir(buildDirectory()); AbstractProcessStep::setupOutputFormatter(formatter); } BuildStepConfigWidget *NimCompilerBuildStep::createConfigWidget() { auto widget = new BuildStepConfigWidget(this); widget->setDisplayName(tr("Nim build step")); widget->setSummaryText(tr("Nim build step")); auto targetComboBox = new QComboBox(widget); auto additionalArgumentsLineEdit = new QLineEdit(widget); auto commandTextEdit = new QTextEdit(widget); commandTextEdit->setEnabled(false); commandTextEdit->setMinimumSize(QSize(0, 0)); auto defaultArgumentsComboBox = new QComboBox(widget); defaultArgumentsComboBox->addItem(tr("None")); defaultArgumentsComboBox->addItem(tr("Debug")); defaultArgumentsComboBox->addItem(tr("Release")); auto formLayout = new QFormLayout(widget); formLayout->addRow(tr("Target:"), targetComboBox); formLayout->addRow(tr("Default arguments:"), defaultArgumentsComboBox); formLayout->addRow(tr("Extra arguments:"), additionalArgumentsLineEdit); formLayout->addRow(tr("Command:"), commandTextEdit); auto updateUi = [=] { const CommandLine cmd = commandLine(); const QStringList parts = QtcProcess::splitArgs(cmd.toUserOutput()); commandTextEdit->setText(parts.join(QChar::LineFeed)); // Re enter the files targetComboBox->clear(); const FilePaths files = project()->files(Project::AllFiles); for (const FilePath &file : files) { if (file.endsWith(".nim")) targetComboBox->addItem(file.fileName(), file.toString()); } const int index = targetComboBox->findData(m_targetNimFile.toString()); targetComboBox->setCurrentIndex(index); const QString text = m_userCompilerOptions.join(QChar::Space); additionalArgumentsLineEdit->setText(text); defaultArgumentsComboBox->setCurrentIndex(m_defaultOptions); }; connect(project(), &Project::fileListChanged, this, updateUi); connect(targetComboBox, QOverload::of(&QComboBox::activated), this, [this, targetComboBox, updateUi] { const QVariant data = targetComboBox->currentData(); m_targetNimFile = FilePath::fromString(data.toString()); updateUi(); }); connect(additionalArgumentsLineEdit, &QLineEdit::textEdited, this, [this, updateUi](const QString &text) { m_userCompilerOptions = text.split(QChar::Space); updateUi(); }); connect(defaultArgumentsComboBox, QOverload::of(&QComboBox::activated), this, [this, updateUi](int index) { m_defaultOptions = static_cast(index); updateUi(); }); updateUi(); return widget; } bool NimCompilerBuildStep::fromMap(const QVariantMap &map) { AbstractProcessStep::fromMap(map); m_userCompilerOptions = map[Constants::C_NIMCOMPILERBUILDSTEP_USERCOMPILEROPTIONS].toString().split('|'); m_defaultOptions = static_cast(map[Constants::C_NIMCOMPILERBUILDSTEP_DEFAULTBUILDOPTIONS].toInt()); m_targetNimFile = FilePath::fromString(map[Constants::C_NIMCOMPILERBUILDSTEP_TARGETNIMFILE].toString()); return true; } QVariantMap NimCompilerBuildStep::toMap() const { QVariantMap result = AbstractProcessStep::toMap(); result[Constants::C_NIMCOMPILERBUILDSTEP_USERCOMPILEROPTIONS] = m_userCompilerOptions.join('|'); result[Constants::C_NIMCOMPILERBUILDSTEP_DEFAULTBUILDOPTIONS] = m_defaultOptions; result[Constants::C_NIMCOMPILERBUILDSTEP_TARGETNIMFILE] = m_targetNimFile.toString(); return result; } bool NimCompilerBuildStep::init() { if (!AbstractProcessStep::init()) return false; setupProcessParameters(processParameters()); return true; } void NimCompilerBuildStep::setBuildType(BuildConfiguration::BuildType buildType) { switch (buildType) { case BuildConfiguration::Release: m_defaultOptions = DefaultBuildOptions::Release; break; case BuildConfiguration::Debug: m_defaultOptions = DefaultBuildOptions::Debug; break; default: m_defaultOptions = DefaultBuildOptions::Empty; break; } updateTargetNimFile(); } CommandLine NimCompilerBuildStep::commandLine() { auto bc = qobject_cast(buildConfiguration()); QTC_ASSERT(bc, return {}); Kit *kit = target()->kit(); auto tc = ToolChainKitAspect::toolChain(kit, Constants::C_NIMLANGUAGE_ID); QTC_ASSERT(tc, return {}); CommandLine cmd{tc->compilerCommand()}; cmd.addArg("c"); if (m_defaultOptions == Release) cmd.addArg("-d:release"); else if (m_defaultOptions == Debug) cmd.addArgs({"--debugInfo", "--lineDir:on"}); cmd.addArg("--out:" + outFilePath().toString()); cmd.addArg("--nimCache:" + bc->cacheDirectory().toString()); for (const QString &arg : m_userCompilerOptions) { if (!arg.isEmpty()) cmd.addArg(arg); } if (!m_targetNimFile.isEmpty()) cmd.addArg(m_targetNimFile.toString()); return cmd; } FilePath NimCompilerBuildStep::outFilePath() const { const QString targetName = m_targetNimFile.toFileInfo().baseName(); return buildDirectory().pathAppended(HostOsInfo::withExecutableSuffix(targetName)); } void NimCompilerBuildStep::updateTargetNimFile() { if (!m_targetNimFile.isEmpty()) return; const FilePaths files = project()->files(Project::AllFiles); for (const FilePath &file : files) { if (file.endsWith(".nim")) { m_targetNimFile = file; break; } } } // NimCompilerBuildStepFactory NimCompilerBuildStepFactory::NimCompilerBuildStepFactory() { registerStep(Constants::C_NIMCOMPILERBUILDSTEP_ID); setDisplayName(NimCompilerBuildStep::tr("Nim Compiler Build Step")); setSupportedStepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD); setSupportedConfiguration(Constants::C_NIMBUILDCONFIGURATION_ID); setRepeatable(false); } } // namespace Nim #ifdef WITH_TESTS #include "nimplugin.h" #include #include namespace Nim { void NimPlugin::testNimParser_data() { QTest::addColumn("input"); QTest::addColumn("inputChannel"); QTest::addColumn("childStdOutLines"); QTest::addColumn("childStdErrLines"); QTest::addColumn("tasks"); QTest::addColumn("outputLines"); // negative tests QTest::newRow("pass-through stdout") << "Sometext" << OutputParserTester::STDOUT << "Sometext\n" << QString() << Tasks() << QString(); QTest::newRow("pass-through stderr") << "Sometext" << OutputParserTester::STDERR << QString() << "Sometext\n" << Tasks() << QString(); // positive tests QTest::newRow("Parse error string") << QString::fromLatin1("main.nim(23, 1) Error: undeclared identifier: 'x'") << OutputParserTester::STDERR << QString() << QString() << Tasks({CompileTask(Task::Error, "Error: undeclared identifier: 'x'", FilePath::fromUserInput("main.nim"), 23)}) << QString(); QTest::newRow("Parse warning string") << QString::fromLatin1("lib/pure/parseopt.nim(56, 34) Warning: quoteIfContainsWhite is deprecated [Deprecated]") << OutputParserTester::STDERR << QString() << QString() << Tasks({CompileTask(Task::Warning, "Warning: quoteIfContainsWhite is deprecated [Deprecated]", FilePath::fromUserInput("lib/pure/parseopt.nim"), 56)}) << QString(); } void NimPlugin::testNimParser() { OutputParserTester testbench; testbench.addLineParser(new NimParser); QFETCH(QString, input); QFETCH(OutputParserTester::Channel, inputChannel); QFETCH(Tasks, tasks); QFETCH(QString, childStdOutLines); QFETCH(QString, childStdErrLines); QFETCH(QString, outputLines); testbench.testParsing(input, inputChannel, tasks, childStdOutLines, childStdErrLines, outputLines); } } #endif