/**************************************************************************** ** ** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** 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 Digia. For licensing terms and ** conditions see http://qt.digia.com/licensing. For further information ** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 2.1 requirements ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Digia gives you certain additional ** rights. These rights are described in the Digia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "qbsproject.h" #include "qbsbuildconfiguration.h" #include "qbslogsink.h" #include "qbsprojectfile.h" #include "qbsprojectmanagerconstants.h" #include "qbsnodes.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 using namespace Core; using namespace ProjectExplorer; using namespace Utils; namespace QbsProjectManager { namespace Internal { // -------------------------------------------------------------------- // Constants: // -------------------------------------------------------------------- static const char CONFIG_CPP_MODULE[] = "cpp"; static const char CONFIG_CXXFLAGS[] = "cxxflags"; static const char CONFIG_CFLAGS[] = "cflags"; static const char CONFIG_DEFINES[] = "defines"; static const char CONFIG_INCLUDEPATHS[] = "includePaths"; static const char CONFIG_FRAMEWORKPATHS[] = "frameworkPaths"; static const char CONFIG_PRECOMPILEDHEADER[] = "precompiledHeader"; static const char CONFIGURATION_PATH[] = ""; // -------------------------------------------------------------------- // QbsProject: // -------------------------------------------------------------------- QbsProject::QbsProject(QbsManager *manager, const QString &fileName) : m_manager(manager), m_projectName(QFileInfo(fileName).completeBaseName()), m_fileName(fileName), m_rootProjectNode(0), m_qbsSetupProjectJob(0), m_qbsUpdateFutureInterface(0), m_currentProgressBase(0), m_forceParsing(false), m_currentBc(0) { m_parsingDelay.setInterval(1000); // delay parsing by 1s. setProjectContext(Context(Constants::PROJECT_ID)); setProjectLanguages(Context(ProjectExplorer::Constants::LANG_CXX)); connect(this, SIGNAL(activeTargetChanged(ProjectExplorer::Target*)), this, SLOT(changeActiveTarget(ProjectExplorer::Target*))); connect(this, SIGNAL(addedTarget(ProjectExplorer::Target*)), this, SLOT(targetWasAdded(ProjectExplorer::Target*))); connect(this, SIGNAL(environmentChanged()), this, SLOT(delayParsing())); connect(&m_parsingDelay, SIGNAL(timeout()), this, SLOT(parseCurrentBuildConfiguration())); updateDocuments(QSet() << fileName); m_rootProjectNode = new QbsProjectNode(this); // needs documents to be initialized! } QbsProject::~QbsProject() { m_codeModelFuture.cancel(); if (m_qbsSetupProjectJob) { m_qbsSetupProjectJob->disconnect(this); m_qbsSetupProjectJob->cancel(); delete m_qbsSetupProjectJob; } } QString QbsProject::displayName() const { return m_projectName; } Id QbsProject::id() const { return Constants::PROJECT_ID; } IDocument *QbsProject::document() const { foreach (IDocument *doc, m_qbsDocuments) { if (doc->filePath() == m_fileName) return doc; } QTC_ASSERT(false, return 0); } QbsManager *QbsProject::projectManager() const { return m_manager; } ProjectNode *QbsProject::rootProjectNode() const { return m_rootProjectNode; } QStringList QbsProject::files(Project::FilesMode fileMode) const { Q_UNUSED(fileMode); QSet result; if (m_rootProjectNode && m_rootProjectNode->qbsProjectData().isValid()) { foreach (const qbs::ProductData &prd, m_rootProjectNode->qbsProjectData().allProducts()) { foreach (const qbs::GroupData &grp, prd.groups()) { foreach (const QString &file, grp.allFilePaths()) result.insert(file); result.insert(grp.location().fileName()); } result.insert(prd.location().fileName()); } result.insert(m_rootProjectNode->qbsProjectData().location().fileName()); } return result.toList(); } void QbsProject::invalidate() { prepareForParsing(); } qbs::BuildJob *QbsProject::build(const qbs::BuildOptions &opts, QStringList productNames) { if (!qbsProject() || isParsing()) return 0; if (productNames.isEmpty()) { return qbsProject()->buildAllProducts(opts); } else { QList products; foreach (const QString &productName, productNames) { bool found = false; foreach (const qbs::ProductData &data, qbsProjectData().allProducts()) { if (data.name() == productName) { found = true; products.append(data); break; } } if (!found) return 0; } return qbsProject()->buildSomeProducts(products, opts); } } qbs::CleanJob *QbsProject::clean(const qbs::CleanOptions &opts) { if (!qbsProject()) return 0; return qbsProject()->cleanAllProducts(opts); } qbs::InstallJob *QbsProject::install(const qbs::InstallOptions &opts) { if (!qbsProject()) return 0; return qbsProject()->installAllProducts(opts); } QString QbsProject::profileForTarget(const Target *t) const { return m_manager->profileForKit(t->kit()); } bool QbsProject::isParsing() const { return m_qbsUpdateFutureInterface; } bool QbsProject::hasParseResult() const { return qbsProject(); } FileName QbsProject::defaultBuildDirectory() const { QFileInfo fi(m_fileName); const QString buildDir = QDir(fi.canonicalPath()).absoluteFilePath(QString::fromLatin1("../%1-build").arg(fi.baseName())); return FileName::fromString(buildDir); } const qbs::Project *QbsProject::qbsProject() const { if (!m_rootProjectNode) return 0; return m_rootProjectNode->qbsProject(); } const qbs::ProjectData QbsProject::qbsProjectData() const { if (!m_rootProjectNode) return qbs::ProjectData(); return m_rootProjectNode->qbsProjectData(); } bool QbsProject::needsSpecialDeployment() const { return true; } void QbsProject::handleQbsParsingDone(bool success) { QTC_ASSERT(m_qbsSetupProjectJob, return); QTC_ASSERT(m_qbsUpdateFutureInterface, return); qbs::Project *project = 0; if (success) { project = new qbs::Project(m_qbsSetupProjectJob->project()); } else { generateErrors(m_qbsSetupProjectJob->error()); m_qbsUpdateFutureInterface->reportCanceled(); } m_qbsSetupProjectJob->deleteLater(); m_qbsSetupProjectJob = 0; m_qbsUpdateFutureInterface->reportFinished(); delete m_qbsUpdateFutureInterface; m_qbsUpdateFutureInterface = 0; m_rootProjectNode->update(project); updateDocuments(project ? project->buildSystemFiles() : QSet() << m_fileName); updateCppCodeModel(m_rootProjectNode->qbsProjectData()); updateQmlJsCodeModel(m_rootProjectNode->qbsProjectData()); updateApplicationTargets(m_rootProjectNode->qbsProjectData()); updateDeploymentInfo(m_rootProjectNode->qbsProject()); foreach (Target *t, targets()) t->updateDefaultRunConfigurations(); emit fileListChanged(); emit projectParsingDone(success); } void QbsProject::handleQbsParsingProgress(int progress) { if (m_qbsUpdateFutureInterface) m_qbsUpdateFutureInterface->setProgressValue(m_currentProgressBase + progress); } void QbsProject::handleQbsParsingTaskSetup(const QString &description, int maximumProgressValue) { Q_UNUSED(description); if (m_qbsUpdateFutureInterface) { m_currentProgressBase = m_qbsUpdateFutureInterface->progressValue(); m_qbsUpdateFutureInterface->setProgressRange(0, m_currentProgressBase + maximumProgressValue); } } void QbsProject::targetWasAdded(Target *t) { connect(t, SIGNAL(activeBuildConfigurationChanged(ProjectExplorer::BuildConfiguration*)), this, SLOT(delayForcedParsing())); connect(t, SIGNAL(buildDirectoryChanged()), this, SLOT(delayForcedParsing())); } void QbsProject::changeActiveTarget(Target *t) { BuildConfiguration *bc = 0; if (t && t->kit()) bc = t->activeBuildConfiguration(); buildConfigurationChanged(bc); } void QbsProject::buildConfigurationChanged(BuildConfiguration *bc) { if (m_currentBc) disconnect(m_currentBc, SIGNAL(qbsConfigurationChanged()), this, SLOT(delayParsing())); m_currentBc = qobject_cast(bc); if (m_currentBc) { connect(m_currentBc, SIGNAL(qbsConfigurationChanged()), this, SLOT(delayParsing())); delayParsing(); } else { invalidate(); } } void QbsProject::delayParsing() { m_parsingDelay.start(); } void QbsProject::delayForcedParsing() { m_forceParsing = true; delayParsing(); } void QbsProject::parseCurrentBuildConfiguration() { m_parsingDelay.stop(); if (!activeTarget()) return; QbsBuildConfiguration *bc = qobject_cast(activeTarget()->activeBuildConfiguration()); if (!bc) return; parse(bc->qbsConfiguration(), bc->environment(), bc->buildDirectory().toString()); } bool QbsProject::fromMap(const QVariantMap &map) { if (!Project::fromMap(map)) return false; Kit *defaultKit = KitManager::defaultKit(); if (!activeTarget() && defaultKit) { Target *t = new Target(this, defaultKit); t->updateDefaultBuildConfigurations(); t->updateDefaultDeployConfigurations(); t->updateDefaultRunConfigurations(); addTarget(t); } return true; } void QbsProject::generateErrors(const qbs::ErrorInfo &e) { foreach (const qbs::ErrorItem &item, e.items()) TaskHub::addTask(Task::Error, item.description(), ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM, FileName::fromString(item.codeLocation().fileName()), item.codeLocation().line()); } void QbsProject::parse(const QVariantMap &config, const Environment &env, const QString &dir) { QTC_ASSERT(!dir.isNull(), return); qbs::SetupProjectParameters params; params.setBuildConfiguration(config); qbs::ErrorInfo err = params.expandBuildConfiguration(m_manager->settings()); if (err.hasError()) { generateErrors(err); return; } // Avoid useless reparsing: const qbs::Project *currentProject = qbsProject(); if (!m_forceParsing && currentProject && currentProject->projectConfiguration() == params.buildConfiguration()) { QHash usedEnv = currentProject->usedEnvironment(); bool canSkip = true; for (QHash::const_iterator i = usedEnv.constBegin(); i != usedEnv.constEnd(); ++i) { if (env.value(i.key()) != i.value()) { canSkip = false; break; } } if (canSkip) return; } params.setBuildRoot(dir); params.setProjectFilePath(m_fileName); params.setIgnoreDifferentProjectFilePath(false); params.setEnvironment(env.toProcessEnvironment()); qbs::Preferences *prefs = QbsManager::preferences(); const QString buildDir = qbsBuildDir(); params.setSearchPaths(prefs->searchPaths(buildDir)); params.setPluginPaths(prefs->pluginPaths(buildDir)); // Do the parsing: prepareForParsing(); QTC_ASSERT(!m_qbsSetupProjectJob, return); m_qbsSetupProjectJob = qbs::Project::setupProject(params, m_manager->logSink(), 0); connect(m_qbsSetupProjectJob, SIGNAL(finished(bool,qbs::AbstractJob*)), this, SLOT(handleQbsParsingDone(bool))); connect(m_qbsSetupProjectJob, SIGNAL(taskStarted(QString,int,qbs::AbstractJob*)), this, SLOT(handleQbsParsingTaskSetup(QString,int))); connect(m_qbsSetupProjectJob, SIGNAL(taskProgress(int,qbs::AbstractJob*)), this, SLOT(handleQbsParsingProgress(int))); emit projectParsingStarted(); } void QbsProject::prepareForParsing() { m_forceParsing = false; TaskHub::clearTasks(ProjectExplorer::Constants::TASK_CATEGORY_BUILDSYSTEM); if (m_qbsUpdateFutureInterface) m_qbsUpdateFutureInterface->reportCanceled(); delete m_qbsUpdateFutureInterface; m_qbsUpdateFutureInterface = 0; if (m_qbsSetupProjectJob) { m_qbsSetupProjectJob->disconnect(this); m_qbsSetupProjectJob->cancel(); m_qbsSetupProjectJob->deleteLater(); m_qbsSetupProjectJob = 0; } m_currentProgressBase = 0; m_qbsUpdateFutureInterface = new QFutureInterface(); m_qbsUpdateFutureInterface->setProgressRange(0, 0); ProgressManager::addTask(m_qbsUpdateFutureInterface->future(), tr("Evaluating"), "Qbs.QbsEvaluate"); m_qbsUpdateFutureInterface->reportStarted(); } void QbsProject::updateDocuments(const QSet &files) { // Update documents: QSet newFiles = files; QTC_ASSERT(!newFiles.isEmpty(), newFiles << m_fileName); QSet oldFiles; foreach (IDocument *doc, m_qbsDocuments) oldFiles.insert(doc->filePath()); QSet filesToAdd = newFiles; filesToAdd.subtract(oldFiles); QSet filesToRemove = oldFiles; filesToRemove.subtract(newFiles); QSet currentDocuments = m_qbsDocuments; foreach (IDocument *doc, currentDocuments) { if (filesToRemove.contains(doc->filePath())) { m_qbsDocuments.remove(doc); delete doc; } } QSet toAdd; foreach (const QString &f, filesToAdd) toAdd.insert(new QbsProjectFile(this, f)); DocumentManager::addDocuments(toAdd.toList()); m_qbsDocuments.unite(toAdd); } void QbsProject::updateCppCodeModel(const qbs::ProjectData &prj) { if (!prj.isValid()) return; Kit *k = 0; QtSupport::BaseQtVersion *qtVersion = 0; if (Target *target = activeTarget()) k = target->kit(); else k = KitManager::defaultKit(); qtVersion = QtSupport::QtKitInformation::qtVersion(k); CppTools::CppModelManagerInterface *modelmanager = CppTools::CppModelManagerInterface::instance(); if (!modelmanager) return; CppTools::CppModelManagerInterface::ProjectInfo pinfo = modelmanager->projectInfo(this); pinfo.clearProjectParts(); CppTools::ProjectPart::QtVersion qtVersionForPart = CppTools::ProjectPart::NoQt; if (qtVersion) { if (qtVersion->qtVersion() < QtSupport::QtVersionNumber(5,0,0)) qtVersionForPart = CppTools::ProjectPart::Qt4; else qtVersionForPart = CppTools::ProjectPart::Qt5; } QHash uiFiles; QStringList allFiles; foreach (const qbs::ProductData &prd, prj.allProducts()) { foreach (const qbs::GroupData &grp, prd.groups()) { const qbs::PropertyMap &props = grp.properties(); const QStringList cxxFlags = props.getModulePropertiesAsStringList( QLatin1String(CONFIG_CPP_MODULE), QLatin1String(CONFIG_CXXFLAGS)); const QStringList cFlags = props.getModulePropertiesAsStringList( QLatin1String(CONFIG_CPP_MODULE), QLatin1String(CONFIG_CFLAGS)); QStringList list = props.getModulePropertiesAsStringList( QLatin1String(CONFIG_CPP_MODULE), QLatin1String(CONFIG_DEFINES)); QByteArray grpDefines; foreach (const QString &def, list) { QByteArray data = def.toUtf8(); int pos = data.indexOf('='); if (pos >= 0) data[pos] = ' '; grpDefines += (QByteArray("#define ") + data + '\n'); } list = props.getModulePropertiesAsStringList(QLatin1String(CONFIG_CPP_MODULE), QLatin1String(CONFIG_INCLUDEPATHS)); QStringList grpIncludePaths; foreach (const QString &p, list) { const QString cp = FileName::fromUserInput(p).toString(); grpIncludePaths.append(cp); } list = props.getModulePropertiesAsStringList(QLatin1String(CONFIG_CPP_MODULE), QLatin1String(CONFIG_FRAMEWORKPATHS)); QStringList grpFrameworkPaths; foreach (const QString &p, list) { const QString cp = FileName::fromUserInput(p).toString(); grpFrameworkPaths.append(cp); } const QString pch = props.getModuleProperty(QLatin1String(CONFIG_CPP_MODULE), QLatin1String(CONFIG_PRECOMPILEDHEADER)).toString(); CppTools::ProjectPart::Ptr part(new CppTools::ProjectPart); part->evaluateToolchain(ToolChainKitInformation::toolChain(k), cxxFlags, cFlags, SysRootKitInformation::sysRoot(k)); CppTools::ProjectFileAdder adder(part->files); foreach (const QString &file, grp.allFilePaths()) { if (file.endsWith(QLatin1String(".ui"))) { QStringList generated = m_rootProjectNode->qbsProject() ->generatedFiles(prd, file, QStringList(QLatin1String("hpp"))); if (generated.count() == 1) uiFiles.insert(file, generated.at(0)); } if (adder.maybeAdd(file)) allFiles.append(file); } part->files << CppTools::ProjectFile(QLatin1String(CONFIGURATION_PATH), CppTools::ProjectFile::CXXHeader); part->qtVersion = qtVersionForPart; part->includePaths += grpIncludePaths; part->frameworkPaths += grpFrameworkPaths; part->precompiledHeaders = QStringList(pch); part->defines += grpDefines; pinfo.appendProjectPart(part); } } setProjectLanguage(ProjectExplorer::Constants::LANG_CXX, !allFiles.isEmpty()); if (pinfo.projectParts().isEmpty()) return; QtSupport::UiCodeModelManager::update(this, uiFiles); // Register update the code model: m_codeModelFuture = modelmanager->updateProjectInfo(pinfo); } void QbsProject::updateQmlJsCodeModel(const qbs::ProjectData &prj) { Q_UNUSED(prj); QmlJS::ModelManagerInterface *modelManager = QmlJS::ModelManagerInterface::instance(); if (!modelManager) return; QmlJS::ModelManagerInterface::ProjectInfo projectInfo = QmlJSTools::defaultProjectInfoForProject(this); setProjectLanguage(ProjectExplorer::Constants::LANG_QMLJS, !projectInfo.sourceFiles.isEmpty()); modelManager->updateProjectInfo(projectInfo); } void QbsProject::updateApplicationTargets(const qbs::ProjectData &projectData) { ProjectExplorer::BuildTargetInfoList applications; foreach (const qbs::ProductData &productData, projectData.allProducts()) { foreach (const qbs::TargetArtifact &ta, productData.targetArtifacts()) { QTC_ASSERT(ta.isValid(), continue); if (!ta.isExecutable()) continue; applications.list << ProjectExplorer::BuildTargetInfo(Utils::FileName::fromString(ta.filePath()), Utils::FileName::fromString(productData.location().fileName())); } } activeTarget()->setApplicationTargets(applications); } void QbsProject::updateDeploymentInfo(const qbs::Project *project) { ProjectExplorer::DeploymentData deploymentData; if (project) { qbs::InstallOptions installOptions; installOptions.setInstallRoot(QLatin1String("/")); foreach (const qbs::InstallableFile &f, project->installableFilesForProject(project->projectData(), installOptions)) { deploymentData.addFile(f.sourceFilePath(), f.targetDirectory(), f.isExecutable() ? ProjectExplorer::DeployableFile::TypeExecutable : ProjectExplorer::DeployableFile::TypeNormal); } } activeTarget()->setDeploymentData(deploymentData); } QString QbsProject::qbsBuildDir() const { QString buildDir = Environment::systemEnvironment().value(QLatin1String("QBS_BUILD_DIR")); if (buildDir.isEmpty()) buildDir = ICore::resourcePath() + QLatin1String("/qbs"); return buildDir; } } // namespace Internal } // namespace QbsProjectManager