1 /****************************************************************************
2 **
3 ** Copyright (C) 2019 Denis Shienkov <denis.shienkov@gmail.com>
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 "baremetalconstants.h"
27 
28 #include "sdccparser.h"
29 #include "sdcctoolchain.h"
30 
31 #include <projectexplorer/abiwidget.h>
32 #include <projectexplorer/projectexplorerconstants.h>
33 #include <projectexplorer/projectmacro.h>
34 #include <projectexplorer/toolchainmanager.h>
35 
36 #include <utils/algorithm.h>
37 #include <utils/environment.h>
38 #include <utils/pathchooser.h>
39 #include <utils/qtcassert.h>
40 #include <utils/qtcprocess.h>
41 
42 #include <QDebug>
43 #include <QDir>
44 #include <QFile>
45 #include <QFileInfo>
46 #include <QFormLayout>
47 #include <QLineEdit>
48 #include <QPlainTextEdit>
49 #include <QSettings>
50 #include <QTemporaryFile>
51 #include <QTextStream>
52 
53 using namespace ProjectExplorer;
54 using namespace Utils;
55 
56 namespace BareMetal {
57 namespace Internal {
58 
59 // Helpers:
60 
compilerExists(const FilePath & compilerPath)61 static bool compilerExists(const FilePath &compilerPath)
62 {
63     const QFileInfo fi = compilerPath.toFileInfo();
64     return fi.exists() && fi.isExecutable() && fi.isFile();
65 }
66 
compilerTargetFlag(const Abi & abi)67 static QString compilerTargetFlag(const Abi &abi)
68 {
69     switch (abi.architecture()) {
70     case Abi::Architecture::Mcs51Architecture:
71         return QString("-mmcs51");
72     case Abi::Architecture::Stm8Architecture:
73         return QString("-mstm8");
74     default:
75         return {};
76     }
77 }
78 
dumpPredefinedMacros(const FilePath & compiler,const Environment & env,const Abi & abi)79 static Macros dumpPredefinedMacros(const FilePath &compiler, const Environment &env,
80                                    const Abi &abi)
81 {
82     if (compiler.isEmpty() || !compiler.toFileInfo().isExecutable())
83         return {};
84 
85     QTemporaryFile fakeIn("XXXXXX.c");
86     if (!fakeIn.open())
87         return {};
88     fakeIn.close();
89 
90     QtcProcess cpp;
91     cpp.setEnvironment(env);
92     cpp.setTimeoutS(10);
93     cpp.setCommand({compiler, {compilerTargetFlag(abi),  "-dM", "-E", fakeIn.fileName()}});
94 
95     cpp.runBlocking();
96     if (cpp.result() != QtcProcess::FinishedWithSuccess) {
97         qWarning() << cpp.exitMessage();
98         return {};
99     }
100 
101     const QByteArray output = cpp.allOutput().toUtf8();
102     return Macro::toMacros(output);
103 }
104 
dumpHeaderPaths(const FilePath & compiler,const Environment & env,const Abi & abi)105 static HeaderPaths dumpHeaderPaths(const FilePath &compiler, const Environment &env,
106                                    const Abi &abi)
107 {
108     if (!compiler.exists())
109         return {};
110 
111     QtcProcess cpp;
112     cpp.setEnvironment(env);
113     cpp.setTimeoutS(10);
114     cpp.setCommand({compiler, {compilerTargetFlag(abi), "--print-search-dirs"}});
115 
116     cpp.runBlocking();
117     if (cpp.result() != QtcProcess::FinishedWithSuccess) {
118         qWarning() << cpp.exitMessage();
119         return {};
120     }
121 
122     QString output = cpp.allOutput();
123     HeaderPaths headerPaths;
124     QTextStream in(&output);
125     QString line;
126     bool synchronized = false;
127     while (in.readLineInto(&line)) {
128         if (!synchronized) {
129             if (line.startsWith("includedir:"))
130                 synchronized = true;
131         } else {
132             if (line.startsWith("programs:") || line.startsWith("datadir:")
133                     || line.startsWith("libdir:") || line.startsWith("libpath:")) {
134                 break;
135             } else {
136                 const QString headerPath = QFileInfo(line.trimmed())
137                         .canonicalFilePath();
138                 headerPaths.append({headerPath, HeaderPathType::BuiltIn});
139             }
140         }
141     }
142     return headerPaths;
143 }
144 
findMacroValue(const Macros & macros,const QByteArray & key)145 static QString findMacroValue(const Macros &macros, const QByteArray &key)
146 {
147     for (const Macro &macro : macros) {
148         if (macro.key == key)
149             return QString::fromLocal8Bit(macro.value);
150     }
151     return {};
152 }
153 
guessVersion(const Macros & macros)154 static QString guessVersion(const Macros &macros)
155 {
156     const QString major = findMacroValue(macros, "__SDCC_VERSION_MAJOR");
157     const QString minor = findMacroValue(macros, "__SDCC_VERSION_MINOR");
158     const QString patch = findMacroValue(macros, "__SDCC_VERSION_PATCH");
159     return QString("%1.%2.%3").arg(major, minor, patch);
160 }
161 
guessArchitecture(const Macros & macros)162 static Abi::Architecture guessArchitecture(const Macros &macros)
163 {
164     for (const Macro &macro : macros) {
165         if (macro.key == "__SDCC_mcs51")
166             return Abi::Architecture::Mcs51Architecture;
167         if (macro.key == "__SDCC_stm8")
168             return Abi::Architecture::Stm8Architecture;
169     }
170     return Abi::Architecture::UnknownArchitecture;
171 }
172 
guessWordWidth(const Macros & macros)173 static unsigned char guessWordWidth(const Macros &macros)
174 {
175     Q_UNUSED(macros)
176     // SDCC always have 16-bit word width.
177     return 16;
178 }
179 
guessFormat(Abi::Architecture arch)180 static Abi::BinaryFormat guessFormat(Abi::Architecture arch)
181 {
182     Q_UNUSED(arch)
183     return Abi::BinaryFormat::UnknownFormat;
184 }
185 
guessAbi(const Macros & macros)186 static Abi guessAbi(const Macros &macros)
187 {
188     const auto arch = guessArchitecture(macros);
189     return {arch, Abi::OS::BareMetalOS, Abi::OSFlavor::GenericFlavor,
190             guessFormat(arch), guessWordWidth(macros)};
191 }
192 
buildDisplayName(Abi::Architecture arch,Utils::Id language,const QString & version)193 static QString buildDisplayName(Abi::Architecture arch, Utils::Id language,
194                                 const QString &version)
195 {
196     const auto archName = Abi::toString(arch);
197     const auto langName = ToolChainManager::displayNameOfLanguageId(language);
198     return SdccToolChain::tr("SDCC %1 (%2, %3)")
199             .arg(version, langName, archName);
200 }
201 
compilerPathFromEnvironment(const QString & compilerName)202 static Utils::FilePath compilerPathFromEnvironment(const QString &compilerName)
203 {
204     const Environment systemEnvironment = Environment::systemEnvironment();
205     return systemEnvironment.searchInPath(compilerName);
206 }
207 
208 // SdccToolChain
209 
SdccToolChain()210 SdccToolChain::SdccToolChain() :
211     ToolChain(Constants::SDCC_TOOLCHAIN_TYPEID)
212 {
213     setTypeDisplayName(Internal::SdccToolChain::tr("SDCC"));
214     setTargetAbiKey("TargetAbi");
215     setCompilerCommandKey("CompilerPath");
216 }
217 
createMacroInspectionRunner() const218 ToolChain::MacroInspectionRunner SdccToolChain::createMacroInspectionRunner() const
219 {
220     Environment env = Environment::systemEnvironment();
221     addToEnvironment(env);
222 
223     const FilePath compiler = compilerCommand();
224     const Id lang = language();
225     const Abi abi = targetAbi();
226 
227     MacrosCache macrosCache = predefinedMacrosCache();
228 
229     return [env, compiler, macrosCache, lang, abi]
230             (const QStringList &flags) {
231         Q_UNUSED(flags)
232 
233         const Macros macros = dumpPredefinedMacros(compiler, env, abi);
234         const auto report = MacroInspectionReport{macros, languageVersion(lang, macros)};
235         macrosCache->insert({}, report);
236 
237         return report;
238     };
239 }
240 
languageExtensions(const QStringList &) const241 Utils::LanguageExtensions SdccToolChain::languageExtensions(const QStringList &) const
242 {
243     return LanguageExtension::None;
244 }
245 
warningFlags(const QStringList & cxxflags) const246 WarningFlags SdccToolChain::warningFlags(const QStringList &cxxflags) const
247 {
248     Q_UNUSED(cxxflags)
249     return WarningFlags::Default;
250 }
251 
createBuiltInHeaderPathsRunner(const Environment &) const252 ToolChain::BuiltInHeaderPathsRunner SdccToolChain::createBuiltInHeaderPathsRunner(
253         const Environment &) const
254 {
255     Environment env = Environment::systemEnvironment();
256     addToEnvironment(env);
257 
258     const FilePath compiler = compilerCommand();
259     const Abi abi = targetAbi();
260 
261     return [env, compiler, abi](const QStringList &, const QString &, const QString &) {
262         return dumpHeaderPaths(compiler, env, abi);
263     };
264 }
265 
addToEnvironment(Environment & env) const266 void SdccToolChain::addToEnvironment(Environment &env) const
267 {
268     if (!compilerCommand().isEmpty()) {
269         const FilePath path = compilerCommand().parentDir();
270         env.prependOrSetPath(path.toString());
271     }
272 }
273 
createOutputParsers() const274 QList<Utils::OutputLineParser *> SdccToolChain::createOutputParsers() const
275 {
276     return {new SdccParser};
277 }
278 
createConfigurationWidget()279 std::unique_ptr<ToolChainConfigWidget> SdccToolChain::createConfigurationWidget()
280 {
281     return std::make_unique<SdccToolChainConfigWidget>(this);
282 }
283 
operator ==(const ToolChain & other) const284 bool SdccToolChain::operator==(const ToolChain &other) const
285 {
286     if (!ToolChain::operator==(other))
287         return false;
288 
289     const auto customTc = static_cast<const SdccToolChain *>(&other);
290     return compilerCommand() == customTc->compilerCommand()
291             && targetAbi() == customTc->targetAbi();
292 }
293 
makeCommand(const Environment & env) const294 FilePath SdccToolChain::makeCommand(const Environment &env) const
295 {
296     Q_UNUSED(env)
297     return {};
298 }
299 
300 // SdccToolChainFactory
301 
SdccToolChainFactory()302 SdccToolChainFactory::SdccToolChainFactory()
303 {
304     setDisplayName(SdccToolChain::tr("SDCC"));
305     setSupportedToolChainType(Constants::SDCC_TOOLCHAIN_TYPEID);
306     setSupportedLanguages({ProjectExplorer::Constants::C_LANGUAGE_ID});
307     setToolchainConstructor([] { return new SdccToolChain; });
308     setUserCreatable(true);
309 }
310 
autoDetect(const QList<ToolChain * > & alreadyKnown,const IDevice::Ptr & device)311 QList<ToolChain *> SdccToolChainFactory::autoDetect(const QList<ToolChain *> &alreadyKnown,
312                                                     const IDevice::Ptr &device)
313 {
314     Q_UNUSED(device)
315     Candidates candidates;
316 
317     if (Utils::HostOsInfo::isWindowsHost()) {
318 
319         // Tries to detect the candidate from the 32-bit
320         // or 64-bit system registry format.
321         auto probeCandidate = [](QSettings::Format format) {
322             QSettings registry("HKEY_LOCAL_MACHINE\\SOFTWARE\\SDCC",
323                                format);
324             QString compilerPath = registry.value("Default").toString();
325             if (compilerPath.isEmpty())
326                 return Candidate{};
327             // Build full compiler path.
328             compilerPath += "/bin/sdcc.exe";
329             const FilePath fn = FilePath::fromString(
330                         QFileInfo(compilerPath).absoluteFilePath());
331             if (!compilerExists(fn))
332                 return Candidate{};
333             // Build compiler version.
334             const QString version = QString("%1.%2.%3").arg(
335                         registry.value("VersionMajor").toString(),
336                         registry.value("VersionMinor").toString(),
337                         registry.value("VersionRevision").toString());
338             return Candidate{fn, version};
339         };
340 
341         const QSettings::Format allowedFormats[] = {
342             QSettings::NativeFormat,
343 #ifdef Q_OS_WIN
344             QSettings::Registry32Format,
345             QSettings::Registry64Format
346 #endif
347         };
348 
349         for (const QSettings::Format format : allowedFormats) {
350             const auto candidate = probeCandidate(format);
351             if (candidate.compilerPath.isEmpty() || candidates.contains(candidate))
352                 continue;
353             candidates.push_back(candidate);
354         }
355     }
356 
357     const FilePath fn = compilerPathFromEnvironment("sdcc");
358     if (fn.exists()) {
359         const Environment env = Environment::systemEnvironment();
360         const Macros macros = dumpPredefinedMacros(fn, env, {});
361         const QString version = guessVersion(macros);
362         const Candidate candidate = {fn, version};
363         if (!candidates.contains(candidate))
364             candidates.push_back(candidate);
365     }
366 
367     return autoDetectToolchains(candidates, alreadyKnown);
368 }
369 
autoDetectToolchains(const Candidates & candidates,const QList<ToolChain * > & alreadyKnown) const370 QList<ToolChain *> SdccToolChainFactory::autoDetectToolchains(
371         const Candidates &candidates, const QList<ToolChain *> &alreadyKnown) const
372 {
373     QList<ToolChain *> result;
374 
375     for (const Candidate &candidate : qAsConst(candidates)) {
376         const QList<ToolChain *> filtered = Utils::filtered(
377                     alreadyKnown, [candidate](ToolChain *tc) {
378             return tc->typeId() == Constants::SDCC_TOOLCHAIN_TYPEID
379                 && tc->compilerCommand() == candidate.compilerPath
380                 && (tc->language() == ProjectExplorer::Constants::C_LANGUAGE_ID);
381         });
382 
383         if (!filtered.isEmpty()) {
384             result << filtered;
385             continue;
386         }
387 
388         // Create toolchain only for C language (because SDCC does not support C++).
389         result << autoDetectToolchain(candidate, ProjectExplorer::Constants::C_LANGUAGE_ID);
390     }
391 
392     return result;
393 }
394 
autoDetectToolchain(const Candidate & candidate,Utils::Id language) const395 QList<ToolChain *> SdccToolChainFactory::autoDetectToolchain(
396         const Candidate &candidate, Utils::Id language) const
397 {
398     const auto env = Environment::systemEnvironment();
399 
400     // Table of supported ABI's by SDCC compiler.
401     const Abi knownAbis[] = {
402         {Abi::Mcs51Architecture},
403         {Abi::Stm8Architecture}
404     };
405 
406     QList<ToolChain *> tcs;
407 
408     // Probe each ABI from the table, because the SDCC compiler
409     // can be compiled with or without the specified architecture.
410     for (const auto &knownAbi : knownAbis) {
411         const Macros macros = dumpPredefinedMacros(candidate.compilerPath, env, knownAbi);
412         if (macros.isEmpty())
413             continue;
414         const Abi abi = guessAbi(macros);
415         if (knownAbi.architecture() != abi.architecture())
416             continue;
417 
418         const auto tc = new SdccToolChain;
419         tc->setDetection(ToolChain::AutoDetection);
420         tc->setLanguage(language);
421         tc->setCompilerCommand(candidate.compilerPath);
422         tc->setTargetAbi(abi);
423         tc->setDisplayName(buildDisplayName(abi.architecture(), language,
424                                             candidate.compilerVersion));
425 
426         const auto languageVersion = ToolChain::languageVersion(language, macros);
427         tc->predefinedMacrosCache()->insert({}, {macros, languageVersion});
428 
429         tcs.push_back(tc);
430     }
431 
432     return tcs;
433 }
434 
435 // SdccToolChainConfigWidget
436 
SdccToolChainConfigWidget(SdccToolChain * tc)437 SdccToolChainConfigWidget::SdccToolChainConfigWidget(SdccToolChain *tc) :
438     ToolChainConfigWidget(tc),
439     m_compilerCommand(new PathChooser),
440     m_abiWidget(new AbiWidget)
441 {
442     m_compilerCommand->setExpectedKind(PathChooser::ExistingCommand);
443     m_compilerCommand->setHistoryCompleter("PE.SDCC.Command.History");
444     m_mainLayout->addRow(tr("&Compiler path:"), m_compilerCommand);
445     m_mainLayout->addRow(tr("&ABI:"), m_abiWidget);
446 
447     m_abiWidget->setEnabled(false);
448 
449     addErrorLabel();
450     setFromToolchain();
451 
452     connect(m_compilerCommand, &PathChooser::rawPathChanged,
453             this, &SdccToolChainConfigWidget::handleCompilerCommandChange);
454     connect(m_abiWidget, &AbiWidget::abiChanged,
455             this, &ToolChainConfigWidget::dirty);
456 }
457 
applyImpl()458 void SdccToolChainConfigWidget::applyImpl()
459 {
460     if (toolChain()->isAutoDetected())
461         return;
462 
463     const auto tc = static_cast<SdccToolChain *>(toolChain());
464     const QString displayName = tc->displayName();
465     tc->setCompilerCommand(m_compilerCommand->filePath());
466     tc->setTargetAbi(m_abiWidget->currentAbi());
467     tc->setDisplayName(displayName);
468 
469     if (m_macros.isEmpty())
470         return;
471 
472     const auto languageVersion = ToolChain::languageVersion(tc->language(), m_macros);
473     tc->predefinedMacrosCache()->insert({}, {m_macros, languageVersion});
474 
475     setFromToolchain();
476 }
477 
isDirtyImpl() const478 bool SdccToolChainConfigWidget::isDirtyImpl() const
479 {
480     const auto tc = static_cast<SdccToolChain *>(toolChain());
481     return m_compilerCommand->filePath() != tc->compilerCommand()
482             || m_abiWidget->currentAbi() != tc->targetAbi()
483             ;
484 }
485 
makeReadOnlyImpl()486 void SdccToolChainConfigWidget::makeReadOnlyImpl()
487 {
488     m_compilerCommand->setReadOnly(true);
489     m_abiWidget->setEnabled(false);
490 }
491 
setFromToolchain()492 void SdccToolChainConfigWidget::setFromToolchain()
493 {
494     const QSignalBlocker blocker(this);
495     const auto tc = static_cast<SdccToolChain *>(toolChain());
496     m_compilerCommand->setFilePath(tc->compilerCommand());
497     m_abiWidget->setAbis({}, tc->targetAbi());
498     const bool haveCompiler = compilerExists(m_compilerCommand->filePath());
499     m_abiWidget->setEnabled(haveCompiler && !tc->isAutoDetected());
500 }
501 
handleCompilerCommandChange()502 void SdccToolChainConfigWidget::handleCompilerCommandChange()
503 {
504     const FilePath compilerPath = m_compilerCommand->filePath();
505     const bool haveCompiler = compilerExists(compilerPath);
506     if (haveCompiler) {
507         const auto env = Environment::systemEnvironment();
508         m_macros = dumpPredefinedMacros(compilerPath, env, {});
509         const Abi guessed = guessAbi(m_macros);
510         m_abiWidget->setAbis({}, guessed);
511     }
512 
513     m_abiWidget->setEnabled(haveCompiler);
514     emit dirty();
515 }
516 
517 } // namespace Internal
518 } // namespace BareMetal
519