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 ¯os, const QByteArray &key)
146 {
147 for (const Macro ¯o : 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 ¯os)
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 ¯os)
163 {
164 for (const Macro ¯o : 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 ¯os)
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 ¯os)
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