// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "defaultpropertyprovider.h" #include "qbskitinformation.h" #include "qbsprojectmanagerconstants.h" #include "qbsprojectmanagertr.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace ProjectExplorer; namespace QbsProjectManager { using namespace Constants; namespace Internal { using namespace ProjectExplorer::Constants; using namespace Ios::Constants; static QString extractToolchainPrefix(QString *compilerName) { QString prefix; const QStringList candidates = {QLatin1String("g++"), QLatin1String("clang++"), QLatin1String("gcc"), QLatin1String("clang")}; for (const QString &candidate : candidates) { const QString suffix = QLatin1Char('-') + candidate; const int suffixIndex = compilerName->lastIndexOf(suffix); if (suffixIndex == -1) continue; prefix = compilerName->left(suffixIndex + 1); compilerName->remove(0, suffixIndex + 1); break; } return prefix; } static QString targetPlatform(const ProjectExplorer::Abi &abi, const ProjectExplorer::Kit *k) { const Utils::Id device = ProjectExplorer::DeviceTypeKitAspect::deviceTypeId(k); switch (abi.os()) { case ProjectExplorer::Abi::WindowsOS: return QLatin1String("windows"); case ProjectExplorer::Abi::DarwinOS: if (device == DESKTOP_DEVICE_TYPE) return QLatin1String("macos"); if (device == IOS_DEVICE_TYPE) return QLatin1String("ios"); if (device == IOS_SIMULATOR_TYPE) return QLatin1String("ios-simulator"); return QLatin1String("darwin"); case ProjectExplorer::Abi::LinuxOS: if (abi.osFlavor() == ProjectExplorer::Abi::AndroidLinuxFlavor) return QLatin1String("android"); return QLatin1String("linux"); case ProjectExplorer::Abi::BsdOS: switch (abi.osFlavor()) { case ProjectExplorer::Abi::FreeBsdFlavor: return QLatin1String("freebsd"); case ProjectExplorer::Abi::NetBsdFlavor: return QLatin1String("netbsd"); case ProjectExplorer::Abi::OpenBsdFlavor: return QLatin1String("openbsd"); default: break; } return QLatin1String("bsd"); case ProjectExplorer::Abi::QnxOS: return QLatin1String("qnx"); case ProjectExplorer::Abi::UnixOS: if (abi.osFlavor() == ProjectExplorer::Abi::SolarisUnixFlavor) return QLatin1String("solaris"); return QLatin1String("unix"); case ProjectExplorer::Abi::VxWorks: return QLatin1String("vxworks"); case ProjectExplorer::Abi::BareMetalOS: case ProjectExplorer::Abi::UnknownOS: break; } return QString(); } static QStringList toolchainList(const ProjectExplorer::ToolChain *tc) { const Utils::Id type = tc->typeId(); if (type == ProjectExplorer::Constants::CLANG_TOOLCHAIN_TYPEID || (type == Android::Constants::ANDROID_TOOLCHAIN_TYPEID && tc->compilerCommand().fileName().contains("clang"))) { return {"clang", "llvm", "gcc"}; } if (type == ProjectExplorer::Constants::GCC_TOOLCHAIN_TYPEID || type == Android::Constants::ANDROID_TOOLCHAIN_TYPEID) { return {"gcc"}; // TODO: Detect llvm-gcc } if (type == ProjectExplorer::Constants::MINGW_TOOLCHAIN_TYPEID) return {"mingw", "gcc"}; if (type == ProjectExplorer::Constants::CLANG_CL_TOOLCHAIN_TYPEID) return {"clang-cl", "msvc"}; if (type == ProjectExplorer::Constants::MSVC_TOOLCHAIN_TYPEID) return {"msvc"}; if (type == BareMetal::Constants::IAREW_TOOLCHAIN_TYPEID) return {"iar"}; if (type == BareMetal::Constants::KEIL_TOOLCHAIN_TYPEID) return {"keil"}; if (type == BareMetal::Constants::SDCC_TOOLCHAIN_TYPEID) return {"sdcc"}; return {}; } static QString architecture(const ProjectExplorer::Abi &targetAbi) { if (targetAbi.architecture() != ProjectExplorer::Abi::UnknownArchitecture) { QString architecture = ProjectExplorer::Abi::toString(targetAbi.architecture()); if (targetAbi.osFlavor() == ProjectExplorer::Abi::AndroidLinuxFlavor) { switch (targetAbi.architecture()) { case ProjectExplorer::Abi::X86Architecture: if (targetAbi.wordWidth() == 64) architecture += "_64"; return architecture; case ProjectExplorer::Abi::ArmArchitecture: if (targetAbi.wordWidth() == 64) architecture += "64"; else architecture += "v7a"; return architecture; default: break; } } // We have to be conservative tacking on suffixes to arch names because an arch that is // already 64-bit may get an incorrect name as a result (i.e. Itanium) if (targetAbi.wordWidth() == 64) { switch (targetAbi.architecture()) { case ProjectExplorer::Abi::X86Architecture: architecture.append(QLatin1Char('_')); [[fallthrough]]; case ProjectExplorer::Abi::ArmArchitecture: // ARM sub-architectures are currently not handled, which is kind of problematic case ProjectExplorer::Abi::MipsArchitecture: case ProjectExplorer::Abi::PowerPCArchitecture: architecture.append(QString::number(targetAbi.wordWidth())); break; default: break; } } return architecture; } return QString(); } static bool isMultiTargetingToolchain(const ProjectExplorer::ToolChain *tc) { // Clang and QCC are multi-targeting compilers; others (GCC/MinGW, MSVC, ICC) are not return tc->targetAbi().os() == ProjectExplorer::Abi::QnxOS || tc->typeId() == ProjectExplorer::Constants::CLANG_TOOLCHAIN_TYPEID; } static QStringList architectures(const ProjectExplorer::ToolChain *tc) { // For platforms which can have builds for multiple architectures in a single configuration // (Darwin, Android), regardless of whether the toolchain is multi-targeting or not (Clang // always is, but Android GCC is not), let qbs automatically determine the list of architectures // to build for by default. Similarly, if the underlying toolchain only targets a single // architecture there's no reason to duplicate the detection logic here. // Handles: GCC/MinGW, ICC, MSVC, Clang (Darwin, Android) if (tc->targetAbi().os() == ProjectExplorer::Abi::DarwinOS || tc->targetAbi().osFlavor() == ProjectExplorer::Abi::AndroidLinuxFlavor || !isMultiTargetingToolchain(tc)) return { }; // This attempts to use the preferred architecture for toolchains which are multi-targeting. // Handles: Clang (Linux/UNIX), QCC const auto arch = architecture(tc->targetAbi()); if (!arch.isEmpty()) return { arch }; return { }; } QVariantMap DefaultPropertyProvider::properties(const ProjectExplorer::Kit *k, const QVariantMap &defaultData) const { QTC_ASSERT(k, return defaultData); QVariantMap data = autoGeneratedProperties(k, defaultData); const QVariantMap customProperties = QbsKitAspect::properties(k); for (QVariantMap::ConstIterator it = customProperties.constBegin(); it != customProperties.constEnd(); ++it) { data.insert(it.key(), it.value()); } return data; } static void filterCompilerLinkerFlags(const ProjectExplorer::Abi &targetAbi, QStringList &flags) { for (int i = 0; i < flags.size(); ) { if (targetAbi.architecture() != ProjectExplorer::Abi::UnknownArchitecture && flags[i] == QStringLiteral("-arch") && i + 1 < flags.size()) { flags.removeAt(i); flags.removeAt(i); } else { ++i; } } } QVariantMap DefaultPropertyProvider::autoGeneratedProperties(const ProjectExplorer::Kit *k, const QVariantMap &defaultData) const { QVariantMap data = defaultData; const QString sysroot = SysRootKitAspect::sysRoot(k).toUserOutput(); if (!sysroot.isEmpty()) data.insert(QLatin1String(QBS_SYSROOT), sysroot); ToolChain *tcC = ToolChainKitAspect::cToolChain(k); ToolChain *tcCxx = ToolChainKitAspect::cxxToolChain(k); if (!tcC && !tcCxx) return data; ToolChain *mainTc = tcCxx ? tcCxx : tcC; Abi targetAbi = mainTc->targetAbi(); auto archs = architectures(mainTc); if (!archs.isEmpty()) data.insert(QLatin1String(QBS_ARCHITECTURES), archs); if (mainTc->targetAbi() != Abi::abiFromTargetTriplet(mainTc->originalTargetTriple()) || targetAbi.osFlavor() == Abi::AndroidLinuxFlavor) { data.insert(QLatin1String(QBS_ARCHITECTURE), architecture(mainTc->targetAbi())); } else if (archs.count() == 1) { data.insert(QLatin1String(QBS_ARCHITECTURE), archs.first()); } data.insert(QLatin1String(QBS_TARGETPLATFORM), targetPlatform(targetAbi, k)); QStringList toolchain = toolchainList(mainTc); if (targetAbi.osFlavor() == Abi::AndroidLinuxFlavor) { const IDevice::ConstPtr dev = DeviceKitAspect::device(k); if (dev) { const QString sdkDir = k->value(Android::Constants::ANDROID_KIT_SDK).toString(); if (!sdkDir.isEmpty()) data.insert("Android.sdk.sdkDir", sdkDir); const QString ndkDir = k->value(Android::Constants::ANDROID_KIT_NDK).toString(); if (!ndkDir.isEmpty()) { data.insert("Android.sdk.ndkDir", ndkDir); data.insert("Android.ndk.ndkDir", ndkDir); } } QtSupport::QtVersion *qtVersion = QtSupport::QtKitAspect::qtVersion(k); if (qtVersion) { data.remove(QBS_ARCHITECTURES); data.remove(QBS_ARCHITECTURE); QStringList abis; for (const auto &abi : qtVersion->qtAbis()) abis << architecture(abi); if (abis.size() == 1) data.insert(QLatin1String(QBS_ARCHITECTURE), abis.first()); else data.insert(QLatin1String(QBS_ARCHITECTURES), abis); } } else { Utils::FilePath cCompilerPath; if (tcC) cCompilerPath = tcC->compilerCommand(); Utils::FilePath cxxCompilerPath; if (tcCxx) cxxCompilerPath = tcCxx->compilerCommand(); QString cCompilerName = cCompilerPath.fileName(); QString cxxCompilerName = cxxCompilerPath.fileName(); const QString cToolchainPrefix = extractToolchainPrefix(&cCompilerName); const QString cxxToolchainPrefix = extractToolchainPrefix(&cxxCompilerName); Utils::FilePath mainFilePath; QString mainCompilerName; QString mainToolchainPrefix; if (tcCxx) { mainFilePath = cxxCompilerPath; mainCompilerName = cxxCompilerName; mainToolchainPrefix = cxxToolchainPrefix; } else { mainFilePath = cCompilerPath; mainCompilerName = cCompilerName; mainToolchainPrefix = cToolchainPrefix; } if (!mainToolchainPrefix.isEmpty()) data.insert(QLatin1String(CPP_TOOLCHAINPREFIX), mainToolchainPrefix); if (toolchain.contains(QLatin1String("clang-cl"))) { data.insert(QLatin1String(CPP_COMPILERNAME), mainCompilerName); const auto clangClToolchain = static_cast(mainTc); data.insert(QLatin1String(CPP_VCVARSALLPATH), clangClToolchain->varsBat()); } else if (toolchain.contains(QLatin1String("msvc"))) { data.insert(QLatin1String(CPP_COMPILERNAME), mainCompilerName); } else { if (!mainCompilerName.isEmpty()) data.insert(QLatin1String(CPP_COMPILERNAME), mainCompilerName); if (!cCompilerName.isEmpty()) data.insert(QLatin1String(CPP_CCOMPILERNAME), cCompilerName); if (!cxxCompilerName.isEmpty()) data.insert(QLatin1String(CPP_CXXCOMPILERNAME), cxxCompilerName); } if (tcC && tcCxx && !cCompilerPath.isEmpty() && !cxxCompilerPath.isEmpty() && cCompilerPath.absolutePath() != cxxCompilerPath.absolutePath()) { Core::MessageManager::writeFlashing( Tr::tr("C and C++ compiler paths differ. C compiler may not work.")); } data.insert(QLatin1String(CPP_TOOLCHAINPATH), mainFilePath.absolutePath().toString()); if (auto gcc = dynamic_cast(mainTc)) { QStringList compilerFlags = gcc->platformCodeGenFlags(); filterCompilerLinkerFlags(targetAbi, compilerFlags); data.insert(QLatin1String(CPP_PLATFORMCOMMONCOMPILERFLAGS), compilerFlags); QStringList linkerFlags = gcc->platformLinkerFlags(); filterCompilerLinkerFlags(targetAbi, linkerFlags); data.insert(QLatin1String(CPP_PLATFORMLINKERFLAGS), linkerFlags); } if (targetAbi.os() == ProjectExplorer::Abi::DarwinOS) { // Reverse engineer the Xcode developer path from the compiler path const QRegularExpression compilerRe( QStringLiteral("^(?.*)/Toolchains/(?:.+)\\.xctoolchain/usr/bin$")); const QRegularExpressionMatch compilerReMatch = compilerRe.match(cxxCompilerPath.absolutePath().toString()); if (compilerReMatch.hasMatch()) { const QString developerPath = compilerReMatch.captured(QStringLiteral("developerpath")); data.insert(QLatin1String(XCODE_DEVELOPERPATH), developerPath); toolchain.insert(0, QStringLiteral("xcode")); // If the sysroot is part of this developer path, set the canonical SDK name const QDir sysrootdir(QDir::cleanPath(sysroot)); const QString sysrootAbs = sysrootdir.absolutePath(); const QSettings sdkSettings( sysrootdir.absoluteFilePath(QStringLiteral("SDKSettings.plist")), QSettings::NativeFormat); const QString version( sdkSettings.value(QStringLiteral("Version")).toString()); QString canonicalName( sdkSettings.value(QStringLiteral("CanonicalName")).toString()); canonicalName.chop(version.size()); if (!canonicalName.isEmpty() && !version.isEmpty() && sysrootAbs.startsWith(developerPath)) { if (sysrootAbs.endsWith(QStringLiteral("/%1.sdk").arg(canonicalName + version), Qt::CaseInsensitive)) { data.insert(QLatin1String(XCODE_SDK), QString(canonicalName + version)); } if (sysrootAbs.endsWith(QStringLiteral("/%1.sdk").arg(canonicalName), Qt::CaseInsensitive)) { data.insert(QLatin1String(XCODE_SDK), canonicalName); } } } } } if (!toolchain.isEmpty()) data.insert(QLatin1String(QBS_TOOLCHAIN), toolchain); return data; } } // namespace Internal } // namespace QbsProjectManager