/**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** 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 "qbsproject.h" #include "qbsbuildconfiguration.h" #include "qbsbuildstep.h" #include "qbsinstallstep.h" #include "qbsnodes.h" #include "qbspmlogging.h" #include "qbsprojectimporter.h" #include "qbsprojectparser.h" #include "qbsprojectmanagerconstants.h" #include "qbsnodetreebuilder.h" #include "qbssession.h" #include "qbssettings.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Core; using namespace ProjectExplorer; using namespace Utils; namespace QbsProjectManager { namespace Internal { // -------------------------------------------------------------------- // Constants: // -------------------------------------------------------------------- class OpTimer { public: OpTimer(const char *name) : m_name(name) { m_timer.start(); } ~OpTimer() { if (qEnvironmentVariableIsSet(Constants::QBS_PROFILING_ENV)) { MessageManager::write(QString("operation %1 took %2ms") .arg(QLatin1String(m_name)).arg(m_timer.elapsed())); } } private: QElapsedTimer m_timer; const char * const m_name; }; // -------------------------------------------------------------------- // QbsProject: // -------------------------------------------------------------------- QbsProject::QbsProject(const FilePath &fileName) : Project(Constants::MIME_TYPE, fileName) { setId(Constants::PROJECT_ID); setProjectLanguages(Context(ProjectExplorer::Constants::CXX_LANGUAGE_ID)); setCanBuildProducts(); setDisplayName(fileName.toFileInfo().completeBaseName()); } QbsProject::~QbsProject() { delete m_importer; } ProjectImporter *QbsProject::projectImporter() const { if (!m_importer) m_importer = new QbsProjectImporter(projectFilePath()); return m_importer; } void QbsProject::configureAsExampleProject() { QList infoList; const QList kits = KitManager::kits(); for (Kit *k : kits) { if (QtSupport::QtKitAspect::qtVersion(k) != nullptr) { if (auto factory = BuildConfigurationFactory::find(k, projectFilePath())) infoList << factory->allAvailableSetups(k, projectFilePath()); } } setup(infoList); if (activeTarget()) static_cast(activeTarget()->buildSystem())->prepareForParsing(); } static bool supportsNodeAction(ProjectAction action, const Node *node) { const auto project = static_cast(node->getProject()); Target *t = project ? project->activeTarget() : nullptr; QbsBuildSystem *bs = t ? static_cast(t->buildSystem()) : nullptr; if (!bs) return false; if (!bs->isProjectEditable()) return false; if (action == RemoveFile || action == Rename) return node->asFileNode(); return false; } QbsBuildSystem::QbsBuildSystem(QbsBuildConfiguration *bc) : BuildSystem(bc->target()), m_session(new QbsSession(this)), m_cppCodeModelUpdater(new CppTools::CppProjectUpdater), m_buildConfiguration(bc) { connect(m_session, &QbsSession::newGeneratedFilesForSources, this, [this](const QHash &generatedFiles) { for (ExtraCompiler * const ec : qAsConst(m_extraCompilers)) ec->deleteLater(); m_extraCompilers.clear(); for (auto it = m_sourcesForGeneratedFiles.cbegin(); it != m_sourcesForGeneratedFiles.cend(); ++it) { for (const QString &sourceFile : it.value()) { const FilePaths generatedFilePaths = transform( generatedFiles.value(sourceFile), [](const QString &s) { return FilePath::fromString(s); }); if (!generatedFilePaths.empty()) { m_extraCompilers.append(it.key()->create( project(), FilePath::fromString(sourceFile), generatedFilePaths)); } } } CppTools::GeneratedCodeModelSupport::update(m_extraCompilers); m_sourcesForGeneratedFiles.clear(); }); connect(m_session, &QbsSession::errorOccurred, this, [](QbsSession::Error e) { const QString msg = tr("Fatal qbs error: %1").arg(QbsSession::errorString(e)); TaskHub::addTask(BuildSystemTask(Task::Error, msg)); }); connect(m_session, &QbsSession::fileListUpdated, this, &QbsBuildSystem::delayParsing); m_parsingDelay.setInterval(1000); // delay parsing by 1s. delayParsing(); connect(bc->project(), &Project::activeTargetChanged, this, &QbsBuildSystem::changeActiveTarget); connect(bc->target(), &Target::activeBuildConfigurationChanged, this, &QbsBuildSystem::delayParsing); connect(&m_parsingDelay, &QTimer::timeout, this, &QbsBuildSystem::triggerParsing); connect(bc->project(), &Project::projectFileIsDirty, this, &QbsBuildSystem::delayParsing); rebuildProjectTree(); } QbsBuildSystem::~QbsBuildSystem() { delete m_cppCodeModelUpdater; delete m_qbsProjectParser; if (m_qbsUpdateFutureInterface) { m_qbsUpdateFutureInterface->reportCanceled(); m_qbsUpdateFutureInterface->reportFinished(); delete m_qbsUpdateFutureInterface; m_qbsUpdateFutureInterface = nullptr; } qDeleteAll(m_extraCompilers); } bool QbsBuildSystem::supportsAction(Node *context, ProjectAction action, const Node *node) const { if (dynamic_cast(context)) { if (action == AddNewFile || action == AddExistingFile) return true; } if (dynamic_cast(context)) { if (action == AddNewFile || action == AddExistingFile) return true; } return supportsNodeAction(action, node); } bool QbsBuildSystem::addFiles(Node *context, const QStringList &filePaths, QStringList *notAdded) { if (auto n = dynamic_cast(context)) { QStringList notAddedDummy; if (!notAdded) notAdded = ¬AddedDummy; const QbsProductNode *prdNode = parentQbsProductNode(n); QTC_ASSERT(prdNode, *notAdded += filePaths; return false); return addFilesToProduct(filePaths, prdNode->productData(), n->groupData(), notAdded); } if (auto n = dynamic_cast(context)) { QStringList notAddedDummy; if (!notAdded) notAdded = ¬AddedDummy; return addFilesToProduct(filePaths, n->productData(), n->mainGroup(), notAdded); } return BuildSystem::addFiles(context, filePaths, notAdded); } RemovedFilesFromProject QbsBuildSystem::removeFiles(Node *context, const QStringList &filePaths, QStringList *notRemoved) { if (auto n = dynamic_cast(context)) { QStringList notRemovedDummy; if (!notRemoved) notRemoved = ¬RemovedDummy; const QbsProductNode * const prdNode = parentQbsProductNode(n); QTC_ASSERT(prdNode, *notRemoved += filePaths; return RemovedFilesFromProject::Error); return removeFilesFromProduct(filePaths, prdNode->productData(), n->groupData(), notRemoved); } if (auto n = dynamic_cast(context)) { QStringList notRemovedDummy; if (!notRemoved) notRemoved = ¬RemovedDummy; return removeFilesFromProduct(filePaths, n->productData(), n->mainGroup(), notRemoved); } return BuildSystem::removeFiles(context, filePaths, notRemoved); } bool QbsBuildSystem::renameFile(Node *context, const QString &filePath, const QString &newFilePath) { if (auto *n = dynamic_cast(context)) { const QbsProductNode * const prdNode = parentQbsProductNode(n); QTC_ASSERT(prdNode, return false); return renameFileInProduct(filePath, newFilePath, prdNode->productData(), n->groupData()); } if (auto *n = dynamic_cast(context)) { return renameFileInProduct(filePath, newFilePath, n->productData(), n->mainGroup()); } return BuildSystem::renameFile(context, filePath, newFilePath); } QVariant QbsBuildSystem::additionalData(Id id) const { if (id == "QmlDesignerImportPath") { QStringList designerImportPaths; const QJsonObject project = session()->projectData(); QStringList paths; forAllProducts(project, [&paths](const QJsonObject &product) { for (const QJsonValue &v : product.value("properties").toObject() .value("qmlDesignerImportPaths").toArray()) { paths << v.toString(); } }); return paths; } return BuildSystem::additionalData(id); } ProjectExplorer::DeploymentKnowledge QbsProject::deploymentKnowledge() const { return DeploymentKnowledge::Perfect; } QStringList QbsBuildSystem::filesGeneratedFrom(const QString &sourceFile) const { return session()->filesGeneratedFrom(sourceFile); } bool QbsBuildSystem::isProjectEditable() const { return !isParsing() && !BuildManager::isBuilding(target()); } bool QbsBuildSystem::ensureWriteableQbsFile(const QString &file) { // Ensure that the file is not read only QFileInfo fi(file); if (!fi.isWritable()) { // Try via vcs manager IVersionControl *versionControl = VcsManager::findVersionControlForDirectory(fi.absolutePath()); if (!versionControl || !versionControl->vcsOpen(file)) { bool makeWritable = QFile::setPermissions(file, fi.permissions() | QFile::WriteUser); if (!makeWritable) { QMessageBox::warning(ICore::mainWindow(), tr("Failed"), tr("Could not write project file %1.").arg(file)); return false; } } } return true; } bool QbsBuildSystem::addFilesToProduct( const QStringList &filePaths, const QJsonObject &product, const QJsonObject &group, QStringList *notAdded) { const QString groupFilePath = group.value("location").toObject().value("file-path").toString(); ensureWriteableQbsFile(groupFilePath); const FileChangeResult result = session()->addFiles( filePaths, product.value("full-display-name").toString(), group.value("name").toString()); if (result.error().hasError()) { MessageManager::write(result.error().toString(), Core::MessageManager::ModeSwitch); *notAdded = result.failedFiles(); } return notAdded->isEmpty(); } RemovedFilesFromProject QbsBuildSystem::removeFilesFromProduct( const QStringList &filePaths, const QJsonObject &product, const QJsonObject &group, QStringList *notRemoved) { const auto allWildcardsInGroup = transform( group.value("source-artifacts-from-wildcards").toArray(), [](const QJsonValue &v) { return v.toObject().value("file-path").toString(); }); QStringList wildcardFiles; QStringList nonWildcardFiles; for (const QString &filePath : filePaths) { if (allWildcardsInGroup.contains(filePath)) wildcardFiles << filePath; else nonWildcardFiles << filePath; } const QString groupFilePath = group.value("location") .toObject().value("file-path").toString(); ensureWriteableQbsFile(groupFilePath); const FileChangeResult result = session()->removeFiles( nonWildcardFiles, product.value("name").toString(), group.value("name").toString()); *notRemoved = result.failedFiles(); if (result.error().hasError()) MessageManager::write(result.error().toString(), Core::MessageManager::ModeSwitch); const bool success = notRemoved->isEmpty(); if (!wildcardFiles.isEmpty()) *notRemoved += wildcardFiles; if (!success) return RemovedFilesFromProject::Error; if (!wildcardFiles.isEmpty()) return RemovedFilesFromProject::Wildcard; return RemovedFilesFromProject::Ok; } bool QbsBuildSystem::renameFileInProduct( const QString &oldPath, const QString &newPath, const QJsonObject &product, const QJsonObject &group) { if (newPath.isEmpty()) return false; QStringList dummy; if (removeFilesFromProduct(QStringList(oldPath), product, group, &dummy) != RemovedFilesFromProject::Ok) { return false; } return addFilesToProduct(QStringList(newPath), product, group, &dummy); } QString QbsBuildSystem::profile() const { return QbsProfileManager::ensureProfileForKit(target()->kit()); } bool QbsBuildSystem::checkCancelStatus() { const CancelStatus cancelStatus = m_cancelStatus; m_cancelStatus = CancelStatusNone; if (cancelStatus != CancelStatusCancelingForReparse) return false; qCDebug(qbsPmLog) << "Cancel request while parsing, starting re-parse"; m_qbsProjectParser->deleteLater(); m_qbsProjectParser = nullptr; m_guard = {}; parseCurrentBuildConfiguration(); return true; } void QbsBuildSystem::updateAfterParse() { qCDebug(qbsPmLog) << "Updating data after parse"; OpTimer opTimer("updateAfterParse"); updateProjectNodes(); updateDocuments(); updateBuildTargetData(); updateCppCodeModel(); updateQmlJsCodeModel(); emit project()->fileListChanged(); m_envCache.clear(); emitBuildSystemUpdated(); } void QbsBuildSystem::delayedUpdateAfterParse() { QTimer::singleShot(0, this, &QbsBuildSystem::updateAfterParse); } void QbsBuildSystem::updateProjectNodes() { OpTimer opTimer("updateProjectNodes"); rebuildProjectTree(); } FilePath QbsBuildSystem::installRoot() { const auto dc = target()->activeDeployConfiguration(); if (dc) { const QList steps = dc->stepList()->steps(); for (const BuildStep * const step : steps) { if (!step->enabled()) continue; if (const auto qbsInstallStep = qobject_cast(step)) return FilePath::fromString(qbsInstallStep->installRoot()); } } const QbsBuildStep * const buildStep = m_buildConfiguration->qbsStep(); return buildStep && buildStep->install() ? buildStep->installRoot() : FilePath(); } void QbsBuildSystem::handleQbsParsingDone(bool success) { QTC_ASSERT(m_qbsProjectParser, return); QTC_ASSERT(m_qbsUpdateFutureInterface, return); qCDebug(qbsPmLog) << "Parsing done, success:" << success; if (checkCancelStatus()) return; generateErrors(m_qbsProjectParser->error()); bool dataChanged = false; bool envChanged = m_lastParseEnv != m_qbsProjectParser->environment(); m_lastParseEnv = m_qbsProjectParser->environment(); if (success) { const QJsonObject projectData = m_qbsProjectParser->session()->projectData(); if (projectData != m_projectData) { m_projectData = projectData; dataChanged = true; } } else { m_qbsUpdateFutureInterface->reportCanceled(); } m_qbsProjectParser->deleteLater(); m_qbsProjectParser = nullptr; m_qbsUpdateFutureInterface->reportFinished(); delete m_qbsUpdateFutureInterface; m_qbsUpdateFutureInterface = nullptr; if (dataChanged) updateAfterParse(); else if (envChanged) updateCppCodeModel(); if (success) m_guard.markAsSuccess(); m_guard = {}; // This one used to change the executable path of a Qbs desktop run configuration // in case the "install" check box in the build step is unchecked and then build // is triggered (which is otherwise a no-op). emitBuildSystemUpdated(); } void QbsBuildSystem::rebuildProjectTree() { std::unique_ptr newRoot = QbsNodeTreeBuilder::buildTree(this); project()->setDisplayName(newRoot->displayName()); setRootProjectNode(std::move(newRoot)); } void QbsBuildSystem::changeActiveTarget(Target *t) { if (t) delayParsing(); } void QbsBuildSystem::triggerParsing() { // Qbs does update the build graph during the build. So we cannot // start to parse while a build is running or we will lose information. if (BuildManager::isBuilding(project())) { scheduleParsing(); return; } parseCurrentBuildConfiguration(); } void QbsBuildSystem::delayParsing() { if (m_buildConfiguration->isActive()) m_parsingDelay.start(); } void QbsBuildSystem::parseCurrentBuildConfiguration() { m_parsingScheduled = false; if (m_cancelStatus == CancelStatusCancelingForReparse) return; // The CancelStatusCancelingAltoghether type can only be set by a build job, during // which no other parse requests come through to this point (except by the build job itself, // but of course not while canceling is in progress). QTC_ASSERT(m_cancelStatus == CancelStatusNone, return); // New parse requests override old ones. // NOTE: We need to wait for the current operation to finish, since otherwise there could // be a conflict. Consider the case where the old qbs::ProjectSetupJob is writing // to the build graph file when the cancel request comes in. If we don't wait for // acknowledgment, it might still be doing that when the new one already reads from the // same file. if (m_qbsProjectParser) { m_cancelStatus = CancelStatusCancelingForReparse; m_qbsProjectParser->cancel(); return; } QVariantMap config = m_buildConfiguration->qbsConfiguration(); if (!config.contains(Constants::QBS_INSTALL_ROOT_KEY)) { config.insert(Constants::QBS_INSTALL_ROOT_KEY, m_buildConfiguration->macroExpander() ->expand(QbsSettings::defaultInstallDirTemplate())); } Environment env = m_buildConfiguration->environment(); QString dir = m_buildConfiguration->buildDirectory().toString(); m_guard = guardParsingRun(); prepareForParsing(); m_parsingDelay.stop(); QTC_ASSERT(!m_qbsProjectParser, return); m_qbsProjectParser = new QbsProjectParser(this, m_qbsUpdateFutureInterface); connect(m_qbsProjectParser, &QbsProjectParser::done, this, &QbsBuildSystem::handleQbsParsingDone); QbsProfileManager::updateProfileIfNecessary(target()->kit()); m_qbsProjectParser->parse(config, env, dir, m_buildConfiguration->configurationName()); } void QbsBuildSystem::cancelParsing() { QTC_ASSERT(m_qbsProjectParser, return); m_cancelStatus = CancelStatusCancelingAltoghether; m_qbsProjectParser->cancel(); } void QbsBuildSystem::updateAfterBuild() { OpTimer opTimer("updateAfterBuild"); const QJsonObject projectData = session()->projectData(); if (projectData == m_projectData) { DeploymentData deploymentDataTmp = deploymentData(); deploymentDataTmp.setLocalInstallRoot(installRoot()); setDeploymentData(deploymentDataTmp); emitBuildSystemUpdated(); return; } qCDebug(qbsPmLog) << "Updating data after build"; m_projectData = projectData; updateProjectNodes(); updateBuildTargetData(); updateCppCodeModel(); // TODO: Should be updateExtraCompilers(). m_envCache.clear(); } void QbsBuildSystem::generateErrors(const ErrorInfo &e) { for (const ErrorInfoItem &item : e.items) { TaskHub::addTask(BuildSystemTask(Task::Error, item.description, item.filePath, item.line)); } } void QbsBuildSystem::prepareForParsing() { TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); if (m_qbsUpdateFutureInterface) { m_qbsUpdateFutureInterface->reportCanceled(); m_qbsUpdateFutureInterface->reportFinished(); } delete m_qbsUpdateFutureInterface; m_qbsUpdateFutureInterface = nullptr; m_qbsUpdateFutureInterface = new QFutureInterface(); m_qbsUpdateFutureInterface->setProgressRange(0, 0); ProgressManager::addTask(m_qbsUpdateFutureInterface->future(), tr("Reading Project \"%1\"").arg(project()->displayName()), "Qbs.QbsEvaluate"); m_qbsUpdateFutureInterface->reportStarted(); } void QbsBuildSystem::updateDocuments() { OpTimer opTimer("updateDocuments"); const FilePath buildDir = FilePath::fromString( m_projectData.value("build-directory").toString()); const auto filePaths = transform>( m_projectData.value("build-system-files").toArray(), [](const QJsonValue &v) { return FilePath::fromString(v.toString()); }); // A changed qbs file (project, module etc) should trigger a re-parse, but not if // the file was generated by qbs itself, in which case that might cause an infinite loop. const QVector nonBuildDirFilePaths = filtered(filePaths, [buildDir](const FilePath &p) { return !p.isChildOf(buildDir); }); project()->setExtraProjectFiles(nonBuildDirFilePaths); } static QString getMimeType(const QJsonObject &sourceArtifact) { const auto tags = sourceArtifact.value("file-tags").toArray(); if (tags.contains("hpp")) { if (CppTools::ProjectFile::isAmbiguousHeader(sourceArtifact.value("file-path").toString())) return QString(CppTools::Constants::AMBIGUOUS_HEADER_MIMETYPE); return QString(CppTools::Constants::CPP_HEADER_MIMETYPE); } if (tags.contains("cpp")) return QString(CppTools::Constants::CPP_SOURCE_MIMETYPE); if (tags.contains("c")) return QString(CppTools::Constants::C_SOURCE_MIMETYPE); if (tags.contains("objc")) return QString(CppTools::Constants::OBJECTIVE_C_SOURCE_MIMETYPE); if (tags.contains("objcpp")) return QString(CppTools::Constants::OBJECTIVE_CPP_SOURCE_MIMETYPE); return {}; } static QString groupLocationToCallGroupId(const QJsonObject &location) { return QString::fromLatin1("%1:%2:%3") .arg(location.value("file-path").toString()) .arg(location.value("line").toString()) .arg(location.value("column").toString()); } // TODO: Receive the values from qbs when QBS-1030 is resolved. static void getExpandedCompilerFlags(QStringList &cFlags, QStringList &cxxFlags, const QJsonObject &properties) { const auto getCppProp = [properties](const char *propertyName) { return properties.value("cpp." + QLatin1String(propertyName)); }; const QJsonValue &enableExceptions = getCppProp("enableExceptions"); const QJsonValue &enableRtti = getCppProp("enableRtti"); QStringList commonFlags = arrayToStringList(getCppProp("platformCommonCompilerFlags")); commonFlags << arrayToStringList(getCppProp("commonCompilerFlags")) << arrayToStringList(getCppProp("platformDriverFlags")) << arrayToStringList(getCppProp("driverFlags")); const QStringList toolchain = arrayToStringList(properties.value("qbs.toolchain")); if (toolchain.contains("gcc")) { bool hasTargetOption = false; if (toolchain.contains("clang")) { const int majorVersion = getCppProp("compilerVersionMajor").toInt(); const int minorVersion = getCppProp("compilerVersionMinor").toInt(); if (majorVersion > 3 || (majorVersion == 3 && minorVersion >= 1)) hasTargetOption = true; } if (hasTargetOption) { commonFlags << "-target" << getCppProp("target").toString(); } else { const QString targetArch = getCppProp("targetArch").toString(); if (targetArch == "x86_64") commonFlags << "-m64"; else if (targetArch == "i386") commonFlags << "-m32"; const QString machineType = getCppProp("machineType").toString(); if (!machineType.isEmpty()) commonFlags << ("-march=" + machineType); } const QStringList targetOS = arrayToStringList(properties.value("qbs.targetOS")); if (targetOS.contains("unix")) { const QVariant positionIndependentCode = getCppProp("positionIndependentCode"); if (!positionIndependentCode.isValid() || positionIndependentCode.toBool()) commonFlags << "-fPIC"; } cFlags = cxxFlags = commonFlags; const auto cxxLanguageVersion = arrayToStringList(getCppProp("cxxLanguageVersion")); if (cxxLanguageVersion.contains("c++17")) cxxFlags << "-std=c++17"; else if (cxxLanguageVersion.contains("c++14")) cxxFlags << "-std=c++14"; else if (cxxLanguageVersion.contains("c++11")) cxxFlags << "-std=c++11"; else if (!cxxLanguageVersion.isEmpty()) cxxFlags << ("-std=" + cxxLanguageVersion.first()); const QString cxxStandardLibrary = getCppProp("cxxStandardLibrary").toString(); if (!cxxStandardLibrary.isEmpty() && toolchain.contains("clang")) cxxFlags << ("-stdlib=" + cxxStandardLibrary); if (!enableExceptions.isUndefined()) { cxxFlags << QLatin1String(enableExceptions.toBool() ? "-fexceptions" : "-fno-exceptions"); } if (!enableRtti.isUndefined()) cxxFlags << QLatin1String(enableRtti.toBool() ? "-frtti" : "-fno-rtti"); const auto cLanguageVersion = arrayToStringList(getCppProp("cLanguageVersion")); if (cLanguageVersion.contains("c11")) cFlags << "-std=c11"; else if (cLanguageVersion.contains("c99")) cFlags << "-std=c99"; else if (!cLanguageVersion.isEmpty()) cFlags << ("-std=" + cLanguageVersion.first()); if (targetOS.contains("darwin")) { const auto darwinVersion = getCppProp("minimumDarwinVersion").toString(); if (!darwinVersion.isEmpty()) { const auto darwinVersionFlag = getCppProp("minimumDarwinVersionCompilerFlag") .toString(); if (!darwinVersionFlag.isEmpty()) cxxFlags << (darwinVersionFlag + '=' + darwinVersion); } } } else if (toolchain.contains("msvc")) { if (enableExceptions.toBool()) { const QString exceptionModel = getCppProp("exceptionHandlingModel").toString(); if (exceptionModel == "default") commonFlags << "/EHsc"; else if (exceptionModel == "seh") commonFlags << "/EHa"; else if (exceptionModel == "externc") commonFlags << "/EHs"; } cFlags = cxxFlags = commonFlags; cFlags << "/TC"; cxxFlags << "/TP"; if (!enableRtti.isUndefined()) cxxFlags << QLatin1String(enableRtti.toBool() ? "/GR" : "/GR-"); if (getCppProp("cxxLanguageVersion").toArray().contains("c++17")) cxxFlags << "/std:c++17"; } } // TODO: Factor out the part that deals with extra compilers. void QbsBuildSystem::updateCppCodeModel() { OpTimer optimer("updateCppCodeModel"); const QJsonObject projectData = session()->projectData(); if (projectData.isEmpty()) return; const QList factories = ExtraCompilerFactory::extraCompilerFactories(); QHash sourcesForGeneratedFiles; m_sourcesForGeneratedFiles.clear(); const QtSupport::CppKitInfo kitInfo(kit()); QTC_ASSERT(kitInfo.isValid(), return); RawProjectParts rpps; forAllProducts(projectData, [&, this](const QJsonObject &prd) { const QString productName = prd.value("full-display-name").toString(); QString cPch; QString cxxPch; QString objcPch; QString objcxxPch; const auto &pchFinder = [&cPch, &cxxPch, &objcPch, &objcxxPch](const QJsonObject &artifact) { const QJsonArray fileTags = artifact.value("file-tags").toArray(); if (fileTags.contains("c_pch_src")) cPch = artifact.value("file-path").toString(); if (fileTags.contains("cpp_pch_src")) cxxPch = artifact.value("file-path").toString(); if (fileTags.contains("objc_pch_src")) objcPch = artifact.value("file-path").toString(); if (fileTags.contains("objcpp_pch_src")) objcxxPch = artifact.value("file-path").toString(); }; forAllArtifacts(prd, ArtifactType::All, pchFinder); const Utils::QtVersion qtVersionForPart = prd.value("module-properties").toObject().value("Qt.core.version").isUndefined() ? Utils::QtVersion::None : kitInfo.projectPartQtVersion; const QJsonArray groups = prd.value("groups").toArray(); for (const QJsonValue &g : groups) { const QJsonObject grp = g.toObject(); const QString groupName = grp.value("name").toString(); RawProjectPart rpp; rpp.setQtVersion(qtVersionForPart); QJsonObject props = grp.value("module-properties").toObject(); if (props.isEmpty()) props = prd.value("module-properties").toObject(); rpp.setCallGroupId(groupLocationToCallGroupId(grp.value("location").toObject())); QStringList cFlags; QStringList cxxFlags; getExpandedCompilerFlags(cFlags, cxxFlags, props); rpp.setFlagsForC({kitInfo.cToolChain, cFlags}); rpp.setFlagsForCxx({kitInfo.cxxToolChain, cxxFlags}); rpp.setMacros(transform(arrayToStringList(props.value("cpp.defines")), [](const QString &s) { return Macro::fromKeyValue(s); })); ProjectExplorer::HeaderPaths grpHeaderPaths; QStringList list = arrayToStringList(props.value("cpp.includePaths")); list.removeDuplicates(); for (const QString &p : qAsConst(list)) grpHeaderPaths += {FilePath::fromUserInput(p).toString(), HeaderPathType::User}; list = arrayToStringList(props.value("cpp.systemIncludePaths")); list.removeDuplicates(); for (const QString &p : qAsConst(list)) grpHeaderPaths += {FilePath::fromUserInput(p).toString(), HeaderPathType::System}; list = arrayToStringList(props.value("cpp.frameworkPaths")); list.append(arrayToStringList(props.value("cpp.systemFrameworkPaths"))); list.removeDuplicates(); for (const QString &p : qAsConst(list)) { grpHeaderPaths += {FilePath::fromUserInput(p).toString(), HeaderPathType::Framework}; } rpp.setHeaderPaths(grpHeaderPaths); rpp.setDisplayName(groupName); const QJsonObject location = grp.value("location").toObject(); rpp.setProjectFileLocation(location.value("file-path").toString(), location.value("line").toInt(), location.value("column").toInt()); rpp.setBuildSystemTarget(productName); rpp.setBuildTargetType(prd.value("is-runnable").toBool() ? BuildTargetType::Executable : BuildTargetType::Library); QHash filePathToSourceArtifact; bool hasCFiles = false; bool hasCxxFiles = false; bool hasObjcFiles = false; bool hasObjcxxFiles = false; const auto artifactWorker = [&, this](const QJsonObject &source) { const QString filePath = source.value("file-path").toString(); filePathToSourceArtifact.insert(filePath, source); for (const QJsonValue &tag : source.value("file-tags").toArray()) { if (tag == "c") hasCFiles = true; else if (tag == "cpp") hasCxxFiles = true; else if (tag == "objc") hasObjcFiles = true; else if (tag == "objcpp") hasObjcxxFiles = true; for (auto i = factories.cbegin(); i != factories.cend(); ++i) { if ((*i)->sourceTag() == tag.toString()) { m_sourcesForGeneratedFiles[*i] << filePath; sourcesForGeneratedFiles[productName] << filePath; } } } }; forAllArtifacts(grp, artifactWorker); QSet pchFiles; if (hasCFiles && props.value("cpp.useCPrecompiledHeader").toBool() && !cPch.isEmpty()) { pchFiles << cPch; } if (hasCxxFiles && props.value("cpp.useCxxPrecompiledHeader").toBool() && !cxxPch.isEmpty()) { pchFiles << cxxPch; } if (hasObjcFiles && props.value("cpp.useObjcPrecompiledHeader").toBool() && !objcPch.isEmpty()) { pchFiles << objcPch; } if (hasObjcxxFiles && props.value("cpp.useObjcxxPrecompiledHeader").toBool() && !objcxxPch.isEmpty()) { pchFiles << objcxxPch; } if (pchFiles.count() > 1) { qCWarning(qbsPmLog) << "More than one pch file enabled for source files in group" << groupName << "in product" << productName; qCWarning(qbsPmLog) << "Expect problems with code model"; } rpp.setPreCompiledHeaders(Utils::toList(pchFiles)); rpp.setFiles(filePathToSourceArtifact.keys(), {}, [filePathToSourceArtifact](const QString &filePath) { // Keep this lambda thread-safe! return getMimeType(filePathToSourceArtifact.value(filePath)); }); rpps.append(rpp); } }); m_cppCodeModelUpdater->update({project(), kitInfo, activeParseEnvironment(), rpps}); if (!sourcesForGeneratedFiles.isEmpty()) session()->requestFilesGeneratedFrom(sourcesForGeneratedFiles); } void QbsBuildSystem::updateQmlJsCodeModel() { OpTimer optimer("updateQmlJsCodeModel"); QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance(); if (!modelManager) return; QmlJS::ModelManagerInterface::ProjectInfo projectInfo = modelManager->defaultProjectInfoForProject(project()); const QJsonObject projectData = session()->projectData(); if (projectData.isEmpty()) return; forAllProducts(projectData, [&projectInfo](const QJsonObject &product) { for (const QJsonValue &path : product.value("properties").toObject() .value("qmlImportPaths").toArray()) { projectInfo.importPaths.maybeInsert(Utils::FilePath::fromString(path.toString()), QmlJS::Dialect::Qml); } }); project()->setProjectLanguage(ProjectExplorer::Constants::QMLJS_LANGUAGE_ID, !projectInfo.sourceFiles.isEmpty()); modelManager->updateProjectInfo(projectInfo, project()); } void QbsBuildSystem::updateApplicationTargets() { QList applications; forAllProducts(session()->projectData(), [this, &applications](const QJsonObject &productData) { if (!productData.value("is-enabled").toBool() || !productData.value("is-runnable").toBool()) return; // TODO: Perhaps put this into a central location instead. Same for module properties etc const auto getProp = [productData](const QString &propName) { return productData.value("properties").toObject().value(propName); }; const bool isQtcRunnable = getProp("qtcRunnable").toBool(); const bool usesTerminal = getProp("consoleApplication").toBool(); const QString projectFile = productData.value("location").toObject() .value("file-path").toString(); QString targetFile; for (const QJsonValue &v : productData.value("generated-artifacts").toArray()) { const QJsonObject artifact = v.toObject(); if (artifact.value("is-target").toBool() && artifact.value("is-executable").toBool()) { targetFile = artifact.value("file-path").toString(); break; } } BuildTargetInfo bti; bti.buildKey = productData.value("full-display-name").toString(); bti.targetFilePath = FilePath::fromString(targetFile); bti.projectFilePath = FilePath::fromString(projectFile); bti.isQtcRunnable = isQtcRunnable; // Fixed up below. bti.usesTerminal = usesTerminal; bti.displayName = bti.buildKey; bti.runEnvModifier = [targetFile, productData, this](Utils::Environment &env, bool usingLibraryPaths) { const QString productName = productData.value("full-display-name").toString(); if (session()->projectData().isEmpty()) return; const QString key = env.toStringList().join(QChar()) + productName + QString::number(usingLibraryPaths); const auto it = m_envCache.constFind(key); if (it != m_envCache.constEnd()) { env = it.value(); return; } QProcessEnvironment procEnv = env.toProcessEnvironment(); procEnv.insert("QBS_RUN_FILE_PATH", targetFile); QStringList setupRunEnvConfig; if (!usingLibraryPaths) setupRunEnvConfig << "ignore-lib-dependencies"; // TODO: It'd be preferable if we could somenow make this asynchronous. RunEnvironmentResult result = session()->getRunEnvironment(productName, procEnv, setupRunEnvConfig); if (result.error().hasError()) { Core::MessageManager::write(tr("Error retrieving run environment: %1") .arg(result.error().toString())); } else { QProcessEnvironment fullEnv = result.environment(); QTC_ASSERT(!fullEnv.isEmpty(), fullEnv = procEnv); env = Utils::Environment(); for (const QString &key : fullEnv.keys()) env.set(key, fullEnv.value(key)); } m_envCache.insert(key, env); }; applications.append(bti); }); setApplicationTargets(applications); } void QbsBuildSystem::updateDeploymentInfo() { if (session()->projectData().isEmpty()) return; DeploymentData deploymentData; forAllProducts(session()->projectData(), [&deploymentData](const QJsonObject &product) { forAllArtifacts(product, ArtifactType::All, [&deploymentData](const QJsonObject &artifact) { const QJsonObject installData = artifact.value("install-data").toObject(); if (installData.value("is-installable").toBool()) { deploymentData.addFile( artifact.value("file-path").toString(), QFileInfo(installData.value("install-file-path").toString()).path(), artifact.value("is-executable").toBool() ? DeployableFile::TypeExecutable : DeployableFile::TypeNormal); } }); }); deploymentData.setLocalInstallRoot(installRoot()); setDeploymentData(deploymentData); } void QbsBuildSystem::updateBuildTargetData() { OpTimer optimer("updateBuildTargetData"); updateApplicationTargets(); updateDeploymentInfo(); // This one used after a normal build. emitBuildSystemUpdated(); } } // namespace Internal } // namespace QbsProjectManager