/**************************************************************************** ** ** Copyright (C) 2021 Denis Shienkov ** Contact: https://www.qt.io/licensing/ ** ** This file is part of Qbs. ** ** $QT_BEGIN_LICENSE:LGPL$ ** 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 Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** 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-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "probe.h" #include "dmcprobe.h" #include "../shared/logging/consolelogger.h" #include #include #include #include #include #include #include #include #include #include using namespace qbs; using Internal::Tr; using Internal::HostOsInfo; namespace { struct Target { QString platform; QString architecture; QString extender; }; } static QStringList knownDmcCompilerNames() { return {QStringLiteral("dmc")}; } static QStringList dumpOutput(const QFileInfo &compiler, const QStringList &flags, const QStringList &keys) { const QString filePath = QDir(QDir::tempPath()).absoluteFilePath( QLatin1String("dmc-dump.c")); QFile fakeIn(filePath); if (!fakeIn.open(QIODevice::Truncate | QIODevice::WriteOnly | QIODevice::Text)) { qbsWarning() << Tr::tr("Unable to open temporary file %1 for output: %2") .arg(fakeIn.fileName(), fakeIn.errorString()); return {}; } fakeIn.write("#define VALUE_TO_STRING(x) #x\n"); fakeIn.write("#define VALUE(x) VALUE_TO_STRING(x)\n"); fakeIn.write("#define VAR_NAME_VALUE(var) \"#define \" #var\" \"VALUE(var)\n"); for (const auto &key : keys) { const auto content = key.toLatin1(); fakeIn.write("#if defined(" + content + ")\n"); fakeIn.write("#pragma message (VAR_NAME_VALUE(" + content + "))\n"); fakeIn.write("#endif\n"); } fakeIn.close(); QStringList args = {QStringLiteral("-e")}; args.reserve(args.size() + int(flags.size())); std::copy(flags.cbegin(), flags.cend(), std::back_inserter(args)); args.push_back(QDir::toNativeSeparators(filePath)); QProcess p; p.start(compiler.absoluteFilePath(), args); p.waitForFinished(3000); fakeIn.remove(); static QRegularExpression re(QLatin1String("\\r?\\n")); const QStringList lines = QString::fromUtf8(p.readAllStandardOutput()).split(re); return lines; } static std::optional dumpDmcTarget(const QFileInfo &compiler, const QStringList &flags) { const QStringList keys = { QStringLiteral("DOS16RM"), QStringLiteral("DOS386"), QStringLiteral("MSDOS"), QStringLiteral("__NT__"), QStringLiteral("_WINDOWS"), }; const auto macros = dumpMacros([&compiler, &flags, &keys]() { return dumpOutput(compiler, flags, keys); }); if (macros.contains(QLatin1String("__NT__"))) { return Target{QLatin1String("windows"), QLatin1String("x86"), QLatin1String("")}; } else if (macros.contains(QLatin1String("_WINDOWS"))) { return Target{QLatin1String("windows"), QLatin1String("x86_16"), QLatin1String("")}; } else if (macros.contains(QLatin1String("DOS386"))) { if (flags.contains(QLatin1String("-mx"))) return Target{QLatin1String("dos"), QLatin1String("x86"), QLatin1String("dosx")}; else if (flags.contains(QLatin1String("-mp"))) return Target{QLatin1String("dos"), QLatin1String("x86"), QLatin1String("dosp")}; } else if (macros.contains(QLatin1String("DOS16RM"))) { if (flags.contains(QLatin1String("-mz"))) return Target{QLatin1String("dos"), QLatin1String("x86_16"), QLatin1String("dosz")}; else if (flags.contains(QLatin1String("-mr"))) return Target{QLatin1String("dos"), QLatin1String("x86_16"), QLatin1String("dosr")}; } else if (macros.contains(QLatin1String("MSDOS"))) { return Target{QLatin1String("dos"), QLatin1String("x86_16"), QLatin1String("")}; } return {}; } static std::vector createDmcProfileHelper(const ToolchainInstallInfo &info, Settings *settings, QStringView profileName = {}) { const QFileInfo compiler = info.compilerPath; std::vector profiles; const QVector probes = { { QStringLiteral("-mn"), QStringLiteral("-WA") }, // Windows NT 32 bit. { QStringLiteral("-ml"), QStringLiteral("-WA") }, // Windows 3.x 16 bit. { QStringLiteral("-mx") }, // DOS with DOSX extender 32 bit. { QStringLiteral("-mp") }, // DOS with Phar Lap extender 32 bit. { QStringLiteral("-mr") }, // DOS with Rational DOS Extender 16 bit. { QStringLiteral("-mz") }, // DOS with ZPM DOS Extender 16 bit. { QStringLiteral("-mc") }, // DOS 16 bit. }; for (const auto &flags : probes) { const auto target = dumpDmcTarget(compiler, flags); if (!target) continue; QString fullProfilename; QString platform = target->extender.isEmpty() ? target->platform : target->extender; if (profileName.isEmpty()) { // Create a full profile name in case we is in auto-detecting mode. if (!info.compilerVersion.isValid()) { fullProfilename = QStringLiteral("dmc-unknown-%1-%2") .arg(platform, target->architecture); } else { const QString version = info.compilerVersion.toString(QLatin1Char('_'), QLatin1Char('_')); fullProfilename = QStringLiteral("dmc-%1-%2-%3") .arg(version, platform, target->architecture); } } else { // Append the detected actual architecture name to the proposed profile name. fullProfilename = QStringLiteral("%1-%2-%3") .arg(profileName, platform, target->architecture); } Profile profile(fullProfilename, settings); profile.setValue(QStringLiteral("cpp.toolchainInstallPath"), compiler.absolutePath()); profile.setValue(QStringLiteral("qbs.toolchainType"), QStringLiteral("dmc")); profile.setValue(QStringLiteral("qbs.architecture"), target->architecture); profile.setValue(QStringLiteral("qbs.targetPlatform"), target->platform); if (!target->extender.isEmpty()) profile.setValue(QStringLiteral("cpp.extenderName"), target->extender); qbsInfo() << Tr::tr("Profile '%1' created for '%2'.") .arg(profile.name(), compiler.absoluteFilePath()); profiles.push_back(std::move(profile)); } return profiles; } static Version dumpDmcVersion(const QFileInfo &compiler) { const QStringList keys = {QStringLiteral("__DMC__")}; const auto macros = dumpMacros([&compiler, keys]() { return dumpOutput(compiler, {}, keys); }); for (const auto ¯o : macros) { if (!macro.startsWith(QLatin1String("0x"))) continue; const int verCode = macro.mid(2).toInt(); return Version{(verCode / 100), (verCode % 100), 0}; } qbsWarning() << Tr::tr("No __DMC__ token was found in the compiler dump"); return Version{}; } static std::vector installedDmcsFromPath() { std::vector infos; const auto compilerNames = knownDmcCompilerNames(); for (const QString &compilerName : compilerNames) { const QFileInfo dmcPath( findExecutable( HostOsInfo::appendExecutableSuffix(compilerName))); if (!dmcPath.exists()) continue; const Version version = dumpDmcVersion(dmcPath); infos.push_back({dmcPath, version}); } std::sort(infos.begin(), infos.end()); return infos; } bool isDmcCompiler(const QString &compilerName) { return Internal::any_of(knownDmcCompilerNames(), [compilerName](const QString &knownName) { return compilerName.contains(knownName); }); } void createDmcProfile(const QFileInfo &compiler, Settings *settings, QStringView profileName) { const ToolchainInstallInfo info = {compiler, Version{}}; createDmcProfileHelper(info, settings, profileName); } void dmcProbe(Settings *settings, std::vector &profiles) { qbsInfo() << Tr::tr("Trying to detect DMC toolchains..."); const std::vector allInfos = installedDmcsFromPath(); if (allInfos.empty()) { qbsInfo() << Tr::tr("No DMC toolchains found."); return; } for (const ToolchainInstallInfo &info : allInfos) { const auto newProfiles = createDmcProfileHelper(info, settings); profiles.reserve(profiles.size() + int(newProfiles.size())); std::copy(newProfiles.cbegin(), newProfiles.cend(), std::back_inserter(profiles)); } }