/**************************************************************************** ** ** Copyright (C) 2019 Denis Shienkov ** Contact: https://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 "baremetalconstants.h" #include "iarewparser.h" #include "iarewtoolchain.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; using namespace Utils; namespace BareMetal { namespace Internal { // Helpers: static const char compilerCommandKeyC[] = "BareMetal.IarToolChain.CompilerPath"; static const char targetAbiKeyC[] = "BareMetal.IarToolChain.TargetAbi"; static bool compilerExists(const FilePath &compilerPath) { const QFileInfo fi = compilerPath.toFileInfo(); return fi.exists() && fi.isExecutable() && fi.isFile(); } static Macros dumpPredefinedMacros(const FilePath &compiler, const Core::Id languageId, const QStringList &env) { if (compiler.isEmpty() || !compiler.toFileInfo().isExecutable()) return {}; // IAR compiler requires an input and output files. QTemporaryFile fakeIn; if (!fakeIn.open()) return {}; fakeIn.close(); const QString outpath = fakeIn.fileName() + ".tmp"; SynchronousProcess cpp; cpp.setEnvironment(env); cpp.setTimeoutS(10); CommandLine cmd(compiler, {fakeIn.fileName()}); if (languageId == ProjectExplorer::Constants::CXX_LANGUAGE_ID) cmd.addArg("--c++"); cmd.addArg("--predef_macros"); cmd.addArg(outpath); const SynchronousProcessResponse response = cpp.runBlocking(cmd); if (response.result != SynchronousProcessResponse::Finished || response.exitCode != 0) { qWarning() << response.exitMessage(cmd.toUserOutput(), 10); return {}; } QByteArray output; QFile fakeOut(outpath); if (fakeOut.open(QIODevice::ReadOnly)) output = fakeOut.readAll(); fakeOut.remove(); return Macro::toMacros(output); } static HeaderPaths dumpHeaderPaths(const FilePath &compiler, const Core::Id languageId, const QStringList &env) { if (!compiler.exists()) return {}; // Seems, that IAR compiler has not options to show a list of system // include directories. But, we can use the following trick to enumerate // this directories. We need to specify the '--preinclude' option with // the wrong value (e.g. a dot). In this case the compiler fails and its // error output will contains a mention about the using search directories // in a form of tokens, like: ' searched: "" '. Where are // the resulting paths are escaped with a quotes. QTemporaryFile fakeIn; if (!fakeIn.open()) return {}; fakeIn.close(); SynchronousProcess cpp; cpp.setEnvironment(env); cpp.setTimeoutS(10); CommandLine cmd(compiler, {fakeIn.fileName()}); if (languageId == ProjectExplorer::Constants::CXX_LANGUAGE_ID) cmd.addArg("--c++"); cmd.addArg("--preinclude"); cmd.addArg("."); // Note: Response should retutn an error, just don't check on errors. const SynchronousProcessResponse response = cpp.runBlocking(cmd); HeaderPaths headerPaths; const QByteArray output = response.allOutput().toUtf8(); for (auto pos = 0; pos < output.size(); ++pos) { const int searchIndex = output.indexOf("searched:", pos); if (searchIndex == -1) break; const int startQuoteIndex = output.indexOf('"', searchIndex + 1); if (startQuoteIndex == -1) break; const int endQuoteIndex = output.indexOf('"', startQuoteIndex + 1); if (endQuoteIndex == -1) break; const QByteArray candidate = output.mid(startQuoteIndex + 1, endQuoteIndex - startQuoteIndex - 1) .simplified(); const QString headerPath = QFileInfo(QFile::decodeName(candidate)) .canonicalFilePath(); // Ignore the QtC binary directory path. if (headerPath != QCoreApplication::applicationDirPath()) headerPaths.append({headerPath, HeaderPathType::BuiltIn}); pos = endQuoteIndex + 1; } return headerPaths; } static Abi::Architecture guessArchitecture(const Macros ¯os) { for (const Macro ¯o : macros) { if (macro.key == "__ICCARM__") return Abi::Architecture::ArmArchitecture; if (macro.key == "__ICC8051__") return Abi::Architecture::Mcs51Architecture; if (macro.key == "__ICCAVR__") return Abi::Architecture::AvrArchitecture; } return Abi::Architecture::UnknownArchitecture; } static unsigned char guessWordWidth(const Macros ¯os) { const Macro sizeMacro = Utils::findOrDefault(macros, [](const Macro &m) { return m.key == "__INT_SIZE__"; }); if (sizeMacro.isValid() && sizeMacro.type == MacroType::Define) return sizeMacro.value.toInt() * 8; return 0; } static Abi::BinaryFormat guessFormat(Abi::Architecture arch) { if (arch == Abi::Architecture::ArmArchitecture) return Abi::BinaryFormat::ElfFormat; if (arch == Abi::Architecture::Mcs51Architecture || arch == Abi::Architecture::AvrArchitecture) { return Abi::BinaryFormat::UbrofFormat; } return Abi::BinaryFormat::UnknownFormat; } static Abi guessAbi(const Macros ¯os) { const auto arch = guessArchitecture(macros); return {arch, Abi::OS::BareMetalOS, Abi::OSFlavor::GenericFlavor, guessFormat(arch), guessWordWidth(macros)}; } static QString buildDisplayName(Abi::Architecture arch, Core::Id language, const QString &version) { const auto archName = Abi::toString(arch); const auto langName = ToolChainManager::displayNameOfLanguageId(language); return IarToolChain::tr("IAREW %1 (%2, %3)") .arg(version, langName, archName); } // IarToolChain IarToolChain::IarToolChain() : ToolChain(Constants::IAREW_TOOLCHAIN_TYPEID) { setTypeDisplayName(Internal::IarToolChainFactory::tr("IAREW")); } void IarToolChain::setTargetAbi(const Abi &abi) { if (abi == m_targetAbi) return; m_targetAbi = abi; toolChainUpdated(); } Abi IarToolChain::targetAbi() const { return m_targetAbi; } bool IarToolChain::isValid() const { return true; } ToolChain::MacroInspectionRunner IarToolChain::createMacroInspectionRunner() const { Environment env = Environment::systemEnvironment(); addToEnvironment(env); const Utils::FilePath compilerCommand = m_compilerCommand; const Core::Id languageId = language(); MacrosCache macrosCache = predefinedMacrosCache(); return [env, compilerCommand, macrosCache, languageId] (const QStringList &flags) { Q_UNUSED(flags) const Macros macros = dumpPredefinedMacros(compilerCommand, languageId, env.toStringList()); const auto languageVersion = ToolChain::languageVersion(languageId, macros); const auto report = MacroInspectionReport{macros, languageVersion}; macrosCache->insert({}, report); return report; }; } Macros IarToolChain::predefinedMacros(const QStringList &cxxflags) const { return createMacroInspectionRunner()(cxxflags).macros; } Utils::LanguageExtensions IarToolChain::languageExtensions(const QStringList &) const { return LanguageExtension::None; } WarningFlags IarToolChain::warningFlags(const QStringList &cxxflags) const { Q_UNUSED(cxxflags); return WarningFlags::Default; } ToolChain::BuiltInHeaderPathsRunner IarToolChain::createBuiltInHeaderPathsRunner( const Environment &) const { Environment env = Environment::systemEnvironment(); addToEnvironment(env); const Utils::FilePath compilerCommand = m_compilerCommand; const Core::Id languageId = language(); HeaderPathsCache headerPaths = headerPathsCache(); return [env, compilerCommand, headerPaths, languageId](const QStringList &flags, const QString &fileName, const QString &) { Q_UNUSED(flags) Q_UNUSED(fileName) const HeaderPaths paths = dumpHeaderPaths(compilerCommand, languageId, env.toStringList()); headerPaths->insert({}, paths); return paths; }; } HeaderPaths IarToolChain::builtInHeaderPaths(const QStringList &cxxFlags, const FilePath &fileName, const Environment &env) const { return createBuiltInHeaderPathsRunner(env)(cxxFlags, fileName.toString(), ""); } void IarToolChain::addToEnvironment(Environment &env) const { if (!m_compilerCommand.isEmpty()) { const FilePath path = m_compilerCommand.parentDir(); env.prependOrSetPath(path.toString()); } } IOutputParser *IarToolChain::outputParser() const { return new IarParser; } QVariantMap IarToolChain::toMap() const { QVariantMap data = ToolChain::toMap(); data.insert(compilerCommandKeyC, m_compilerCommand.toString()); data.insert(targetAbiKeyC, m_targetAbi.toString()); return data; } bool IarToolChain::fromMap(const QVariantMap &data) { if (!ToolChain::fromMap(data)) return false; m_compilerCommand = FilePath::fromString(data.value(compilerCommandKeyC).toString()); m_targetAbi = Abi::fromString(data.value(targetAbiKeyC).toString()); return true; } std::unique_ptr IarToolChain::createConfigurationWidget() { return std::make_unique(this); } bool IarToolChain::operator==(const ToolChain &other) const { if (!ToolChain::operator==(other)) return false; const auto customTc = static_cast(&other); return m_compilerCommand == customTc->m_compilerCommand && m_targetAbi == customTc->m_targetAbi ; } void IarToolChain::setCompilerCommand(const FilePath &file) { if (file == m_compilerCommand) return; m_compilerCommand = file; toolChainUpdated(); } FilePath IarToolChain::compilerCommand() const { return m_compilerCommand; } FilePath IarToolChain::makeCommand(const Environment &env) const { Q_UNUSED(env) return {}; } // IarToolChainFactory IarToolChainFactory::IarToolChainFactory() { setDisplayName(tr("IAREW")); setSupportedToolChainType(Constants::IAREW_TOOLCHAIN_TYPEID); setSupportedLanguages({ProjectExplorer::Constants::C_LANGUAGE_ID, ProjectExplorer::Constants::CXX_LANGUAGE_ID}); setToolchainConstructor([] { return new IarToolChain; }); setUserCreatable(true); } QList IarToolChainFactory::autoDetect(const QList &alreadyKnown) { Candidates candidates; #ifdef Q_OS_WIN #ifdef Q_OS_WIN64 static const char kRegistryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\WOW6432Node\\IAR Systems\\Embedded Workbench"; #else static const char kRegistryNode[] = "HKEY_LOCAL_MACHINE\\SOFTWARE\\IAR Systems\\Embedded Workbench"; #endif // Dictionary for know toolchains. static const struct Entry { QString registryKey; QString subExePath; } knowToolchains[] = { {{"EWARM"}, {"\\arm\\bin\\iccarm.exe"}}, {{"EWAVR"}, {"\\avr\\bin\\iccavr.exe"}}, {{"EW8051"}, {"\\8051\\bin\\icc8051.exe"}}, }; QSettings registry(kRegistryNode, QSettings::NativeFormat); const auto oneLevelGroups = registry.childGroups(); for (const QString &oneLevelKey : oneLevelGroups) { registry.beginGroup(oneLevelKey); const auto twoLevelGroups = registry.childGroups(); for (const Entry &entry : knowToolchains) { if (twoLevelGroups.contains(entry.registryKey)) { registry.beginGroup(entry.registryKey); const auto threeLevelGroups = registry.childGroups(); for (const QString &threeLevelKey : threeLevelGroups) { registry.beginGroup(threeLevelKey); QString compilerPath = registry.value("InstallPath").toString(); if (!compilerPath.isEmpty()) { // Build full compiler path. compilerPath += entry.subExePath; const FileName fn = FileName::fromString(compilerPath); if (compilerExists(fn)) { // Note: threeLevelKey is a guessed toolchain version. candidates.push_back({fn, threeLevelKey}); } } registry.endGroup(); } registry.endGroup(); } } registry.endGroup(); } #endif // Q_OS_WIN return autoDetectToolchains(candidates, alreadyKnown); } QList IarToolChainFactory::autoDetectToolchains( const Candidates &candidates, const QList &alreadyKnown) const { QList result; for (const Candidate &candidate : qAsConst(candidates)) { const QList filtered = Utils::filtered( alreadyKnown, [candidate](ToolChain *tc) { return tc->typeId() == Constants::IAREW_TOOLCHAIN_TYPEID && tc->compilerCommand() == candidate.compilerPath && (tc->language() == ProjectExplorer::Constants::C_LANGUAGE_ID || tc->language() == ProjectExplorer::Constants::CXX_LANGUAGE_ID); }); if (!filtered.isEmpty()) { result << filtered; continue; } // Create toolchains for both C and C++ languages. result << autoDetectToolchain(candidate, ProjectExplorer::Constants::C_LANGUAGE_ID); result << autoDetectToolchain(candidate, ProjectExplorer::Constants::CXX_LANGUAGE_ID); } return result; } QList IarToolChainFactory::autoDetectToolchain( const Candidate &candidate, Core::Id languageId) const { const auto env = Environment::systemEnvironment(); const Macros macros = dumpPredefinedMacros(candidate.compilerPath, languageId, env.toStringList()); if (macros.isEmpty()) return {}; const Abi abi = guessAbi(macros); const auto tc = new IarToolChain; tc->setDetection(ToolChain::AutoDetection); tc->setLanguage(languageId); tc->setCompilerCommand(candidate.compilerPath); tc->setTargetAbi(abi); tc->setDisplayName(buildDisplayName(abi.architecture(), languageId, candidate.compilerVersion)); const auto languageVersion = ToolChain::languageVersion(languageId, macros); tc->predefinedMacrosCache()->insert({}, {macros, languageVersion}); return {tc}; } // IarToolChainConfigWidget IarToolChainConfigWidget::IarToolChainConfigWidget(IarToolChain *tc) : ToolChainConfigWidget(tc), m_compilerCommand(new PathChooser), m_abiWidget(new AbiWidget) { m_compilerCommand->setExpectedKind(PathChooser::ExistingCommand); m_compilerCommand->setHistoryCompleter("PE.IAREW.Command.History"); m_mainLayout->addRow(tr("&Compiler path:"), m_compilerCommand); m_mainLayout->addRow(tr("&ABI:"), m_abiWidget); m_abiWidget->setEnabled(false); addErrorLabel(); setFromToolchain(); connect(m_compilerCommand, &PathChooser::rawPathChanged, this, &IarToolChainConfigWidget::handleCompilerCommandChange); connect(m_abiWidget, &AbiWidget::abiChanged, this, &ToolChainConfigWidget::dirty); } void IarToolChainConfigWidget::applyImpl() { if (toolChain()->isAutoDetected()) return; const auto tc = static_cast(toolChain()); const QString displayName = tc->displayName(); tc->setCompilerCommand(m_compilerCommand->fileName()); tc->setTargetAbi(m_abiWidget->currentAbi()); tc->setDisplayName(displayName); if (m_macros.isEmpty()) return; const auto languageVersion = ToolChain::languageVersion(tc->language(), m_macros); tc->predefinedMacrosCache()->insert({}, {m_macros, languageVersion}); setFromToolchain(); } bool IarToolChainConfigWidget::isDirtyImpl() const { const auto tc = static_cast(toolChain()); return m_compilerCommand->fileName() != tc->compilerCommand() || m_abiWidget->currentAbi() != tc->targetAbi() ; } void IarToolChainConfigWidget::makeReadOnlyImpl() { m_compilerCommand->setReadOnly(true); m_abiWidget->setEnabled(false); } void IarToolChainConfigWidget::setFromToolchain() { const QSignalBlocker blocker(this); const auto tc = static_cast(toolChain()); m_compilerCommand->setFileName(tc->compilerCommand()); m_abiWidget->setAbis({}, tc->targetAbi()); const bool haveCompiler = compilerExists(m_compilerCommand->fileName()); m_abiWidget->setEnabled(haveCompiler && !tc->isAutoDetected()); } void IarToolChainConfigWidget::handleCompilerCommandChange() { const FilePath compilerPath = m_compilerCommand->fileName(); const bool haveCompiler = compilerExists(compilerPath); if (haveCompiler) { const auto env = Environment::systemEnvironment(); const auto languageId = toolChain()->language(); m_macros = dumpPredefinedMacros(compilerPath, languageId, env.toStringList()); const Abi guessed = guessAbi(m_macros); m_abiWidget->setAbis({}, guessed); } m_abiWidget->setEnabled(haveCompiler); emit dirty(); } } // namespace Internal } // namespace BareMetal