/**************************************************************************** ** ** Copyright (C) 2015 The Qt Company Ltd. ** Contact: http://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 http://www.qt.io/terms-conditions. For further information ** use the contact form at http://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 2.1 or version 3 as published by the Free ** Software Foundation and appearing in the file LICENSE.LGPLv21 and ** LICENSE.LGPLv3 included in the packaging of this file. Please review the ** following information to ensure the GNU Lesser General Public License ** requirements will be met: https://www.gnu.org/licenses/lgpl.html and ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, The Qt Company gives you certain additional ** rights. These rights are described in The Qt Company LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ****************************************************************************/ #include "cmakeproject.h" #include "cmakebuildconfiguration.h" #include "cmakeprojectconstants.h" #include "cmakeprojectnodes.h" #include "cmakerunconfiguration.h" #include "makestep.h" #include "cmakeopenprojectwizard.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 using namespace CMakeProjectManager; using namespace CMakeProjectManager::Internal; using namespace ProjectExplorer; // QtCreator CMake Generator wishlist: // Which make targets we need to build to get all executables // What is the make we need to call // What is the actual compiler executable // DEFINES // Open Questions // Who sets up the environment for cl.exe ? INCLUDEPATH and so on /*! \class CMakeProject */ CMakeProject::CMakeProject(CMakeManager *manager, const QString &fileName) : m_manager(manager), m_activeTarget(0), m_fileName(fileName), m_rootNode(new CMakeProjectNode(fileName)), m_watcher(new QFileSystemWatcher(this)) { setId(Constants::CMAKEPROJECT_ID); setProjectContext(Core::Context(CMakeProjectManager::Constants::PROJECTCONTEXT)); setProjectLanguages(Core::Context(ProjectExplorer::Constants::LANG_CXX)); m_projectName = QFileInfo(fileName).absoluteDir().dirName(); m_file = new CMakeFile(this, fileName); connect(this, SIGNAL(buildTargetsChanged()), this, SLOT(updateRunConfigurations())); connect(m_watcher, SIGNAL(fileChanged(QString)), this, SLOT(fileChanged(QString))); } CMakeProject::~CMakeProject() { m_codeModelFuture.cancel(); delete m_rootNode; } void CMakeProject::fileChanged(const QString &fileName) { Q_UNUSED(fileName) parseCMakeLists(); } void CMakeProject::changeActiveBuildConfiguration(ProjectExplorer::BuildConfiguration *bc) { if (!bc) return; CMakeBuildConfiguration *cmakebc = static_cast(bc); // Pop up a dialog asking the user to rerun cmake QString cbpFile = CMakeManager::findCbpFile(QDir(bc->buildDirectory().toString())); QFileInfo cbpFileFi(cbpFile); CMakeOpenProjectWizard::Mode mode = CMakeOpenProjectWizard::Nothing; if (!cbpFileFi.exists()) { mode = CMakeOpenProjectWizard::NeedToCreate; } else { foreach (const QString &file, m_watchedFiles) { if (QFileInfo(file).lastModified() > cbpFileFi.lastModified()) { mode = CMakeOpenProjectWizard::NeedToUpdate; break; } } } if (mode != CMakeOpenProjectWizard::Nothing) { CMakeBuildInfo info(cmakebc); CMakeOpenProjectWizard copw(Core::ICore::mainWindow(), m_manager, mode, &info); if (copw.exec() == QDialog::Accepted) cmakebc->setUseNinja(copw.useNinja()); // NeedToCreate can change the Ninja setting } // reparse parseCMakeLists(); } void CMakeProject::activeTargetWasChanged(Target *target) { if (m_activeTarget) { disconnect(m_activeTarget, SIGNAL(activeBuildConfigurationChanged(ProjectExplorer::BuildConfiguration*)), this, SLOT(changeActiveBuildConfiguration(ProjectExplorer::BuildConfiguration*))); } m_activeTarget = target; if (!m_activeTarget) return; connect(m_activeTarget, SIGNAL(activeBuildConfigurationChanged(ProjectExplorer::BuildConfiguration*)), this, SLOT(changeActiveBuildConfiguration(ProjectExplorer::BuildConfiguration*))); changeActiveBuildConfiguration(m_activeTarget->activeBuildConfiguration()); } void CMakeProject::changeBuildDirectory(CMakeBuildConfiguration *bc, const QString &newBuildDirectory) { bc->setBuildDirectory(Utils::FileName::fromString(newBuildDirectory)); parseCMakeLists(); } QStringList CMakeProject::getCXXFlagsFor(const CMakeBuildTarget &buildTarget) { QString makeCommand = QDir::fromNativeSeparators(buildTarget.makeCommand); int startIndex = makeCommand.indexOf(QLatin1Char('\"')); int endIndex = makeCommand.indexOf(QLatin1Char('\"'), startIndex + 1); if (startIndex != -1 && endIndex != -1) { startIndex += 1; QString makefile = makeCommand.mid(startIndex, endIndex - startIndex); int slashIndex = makefile.lastIndexOf(QLatin1Char('/')); makefile.truncate(slashIndex); makefile.append(QLatin1String("/CMakeFiles/") + buildTarget.title + QLatin1String(".dir/flags.make")); QFile file(makefile); if (file.exists()) { file.open(QIODevice::ReadOnly | QIODevice::Text); QTextStream stream(&file); while (!stream.atEnd()) { QString line = stream.readLine().trimmed(); if (line.startsWith(QLatin1String("CXX_FLAGS ="))) { // Skip past = return line.mid(11).trimmed().split(QLatin1Char(' '), QString::SkipEmptyParts); } } } } // Attempt to find build.ninja file and obtain FLAGS (CXX_FLAGS) from there if no suitable flags.make were // found // Get "all" target's working directory QString buildNinjaFile = QDir::fromNativeSeparators(buildTarget.workingDirectory); buildNinjaFile += QLatin1String("/build.ninja"); QFile buildNinja(buildNinjaFile); if (buildNinja.exists()) { buildNinja.open(QIODevice::ReadOnly | QIODevice::Text); QTextStream stream(&buildNinja); bool cxxFound = false; while (!stream.atEnd()) { QString line = stream.readLine().trimmed(); // Look for a build rule which invokes CXX_COMPILER if (line.startsWith(QLatin1String("build"))) { cxxFound = line.indexOf(QLatin1String("CXX_COMPILER")) != -1; } else if (cxxFound && line.startsWith(QLatin1String("FLAGS ="))) { // Skip past = return line.mid(7).trimmed().split(QLatin1Char(' '), QString::SkipEmptyParts); } } } return QStringList(); } bool CMakeProject::parseCMakeLists() { if (!activeTarget() || !activeTarget()->activeBuildConfiguration()) { return false; } CMakeBuildConfiguration *activeBC = static_cast(activeTarget()->activeBuildConfiguration()); foreach (Core::IDocument *document, Core::DocumentModel::openedDocuments()) if (isProjectFile(document->filePath())) document->infoBar()->removeInfo("CMakeEditor.RunCMake"); // Find cbp file QString cbpFile = CMakeManager::findCbpFile(activeBC->buildDirectory().toString()); if (cbpFile.isEmpty()) { emit buildTargetsChanged(); return false; } // setFolderName m_rootNode->setDisplayName(QFileInfo(cbpFile).completeBaseName()); CMakeCbpParser cbpparser; // Parsing //qDebug()<<"Parsing file "<files()) if (file != cbpFile) m_watcher->removePath(file); // how can we ensure that it is completely written? m_watcher->addPath(cbpFile); m_projectName = cbpparser.projectName(); m_rootNode->setDisplayName(cbpparser.projectName()); //qDebug()<<"Building Tree"; QList fileList = cbpparser.fileList(); QSet projectFiles; if (cbpparser.hasCMakeFiles()) { fileList.append(cbpparser.cmakeFileList()); foreach (const ProjectExplorer::FileNode *node, cbpparser.cmakeFileList()) projectFiles.insert(node->path()); } else { // Manually add the CMakeLists.txt file QString cmakeListTxt = projectDirectory().toString() + QLatin1String("/CMakeLists.txt"); bool generated = false; fileList.append(new ProjectExplorer::FileNode(cmakeListTxt, ProjectExplorer::ProjectFileType, generated)); projectFiles.insert(cmakeListTxt); } m_watchedFiles = projectFiles; m_files.clear(); foreach (ProjectExplorer::FileNode *fn, fileList) m_files.append(fn->path()); m_files.sort(); buildTree(m_rootNode, fileList); //qDebug()<<"Adding Targets"; m_buildTargets = cbpparser.buildTargets(); // qDebug()<<"Printing targets"; // foreach (CMakeBuildTarget ct, m_buildTargets) { // qDebug()<kit(); ToolChain *tc = ProjectExplorer::ToolChainKitInformation::toolChain(k); if (!tc) { emit buildTargetsChanged(); emit fileListChanged(); return true; } CppTools::CppModelManager *modelmanager = CppTools::CppModelManager::instance(); if (modelmanager) { CppTools::ProjectInfo pinfo = modelmanager->projectInfo(this); pinfo.clearProjectParts(); CppTools::ProjectPartBuilder ppBuilder(pinfo); foreach (const CMakeBuildTarget &cbt, m_buildTargets) { // This explicitly adds -I. to the include paths QStringList includePaths = cbt.includeFiles; includePaths += projectDirectory().toString(); ppBuilder.setIncludePaths(includePaths); ppBuilder.setCFlags(getCXXFlagsFor(cbt)); ppBuilder.setCxxFlags(getCXXFlagsFor(cbt)); ppBuilder.setDefines(cbt.defines); ppBuilder.setDisplayName(cbt.title); const QList languages = ppBuilder.createProjectPartsForFiles(cbt.files); foreach (Core::Id language, languages) setProjectLanguage(language, true); } m_codeModelFuture.cancel(); pinfo.finish(); m_codeModelFuture = modelmanager->updateProjectInfo(pinfo); } emit displayNameChanged(); emit buildTargetsChanged(); emit fileListChanged(); emit activeBC->emitBuildTypeChanged(); return true; } bool CMakeProject::isProjectFile(const QString &fileName) { return m_watchedFiles.contains(fileName); } QList CMakeProject::buildTargets() const { return m_buildTargets; } QStringList CMakeProject::buildTargetTitles(bool runnable) const { QStringList results; foreach (const CMakeBuildTarget &ct, m_buildTargets) { if (runnable && (ct.executable.isEmpty() || ct.library)) continue; results << ct.title; } return results; } bool CMakeProject::hasBuildTarget(const QString &title) const { foreach (const CMakeBuildTarget &ct, m_buildTargets) { if (ct.title == title) return true; } return false; } void CMakeProject::gatherFileNodes(ProjectExplorer::FolderNode *parent, QList &list) { foreach (ProjectExplorer::FolderNode *folder, parent->subFolderNodes()) gatherFileNodes(folder, list); foreach (ProjectExplorer::FileNode *file, parent->fileNodes()) list.append(file); } bool sortNodesByPath(Node *a, Node *b) { return a->path() < b->path(); } void CMakeProject::buildTree(CMakeProjectNode *rootNode, QList newList) { // Gather old list QList oldList; gatherFileNodes(rootNode, oldList); Utils::sort(oldList, sortNodesByPath); Utils::sort(newList, sortNodesByPath); QList added; QList deleted; ProjectExplorer::compareSortedLists(oldList, newList, deleted, added, sortNodesByPath); qDeleteAll(ProjectExplorer::subtractSortedList(newList, added, sortNodesByPath)); // add added nodes foreach (ProjectExplorer::FileNode *fn, added) { // qDebug()<<"added"<path(); // Get relative path to rootNode QString parentDir = QFileInfo(fn->path()).absolutePath(); ProjectExplorer::FolderNode *folder = findOrCreateFolder(rootNode, parentDir); folder->addFileNodes(QList()<< fn); } // remove old file nodes and check whether folder nodes can be removed foreach (ProjectExplorer::FileNode *fn, deleted) { ProjectExplorer::FolderNode *parent = fn->parentFolderNode(); // qDebug()<<"removed"<path(); parent->removeFileNodes(QList() << fn); // Check for empty parent while (parent->subFolderNodes().isEmpty() && parent->fileNodes().isEmpty()) { ProjectExplorer::FolderNode *grandparent = parent->parentFolderNode(); grandparent->removeFolderNodes(QList() << parent); parent = grandparent; if (parent == rootNode) break; } } } ProjectExplorer::FolderNode *CMakeProject::findOrCreateFolder(CMakeProjectNode *rootNode, QString directory) { QString relativePath = QDir(QFileInfo(rootNode->path()).path()).relativeFilePath(directory); QStringList parts = relativePath.split(QLatin1Char('/'), QString::SkipEmptyParts); ProjectExplorer::FolderNode *parent = rootNode; QString path = QFileInfo(rootNode->path()).path(); foreach (const QString &part, parts) { path += QLatin1Char('/'); path += part; // Find folder in subFolders bool found = false; foreach (ProjectExplorer::FolderNode *folder, parent->subFolderNodes()) { if (folder->path() == path) { // yeah found something :) parent = folder; found = true; break; } } if (!found) { // No FolderNode yet, so create it ProjectExplorer::FolderNode *tmp = new ProjectExplorer::FolderNode(path); tmp->setDisplayName(part); parent->addFolderNodes(QList() << tmp); parent = tmp; } } return parent; } QString CMakeProject::displayName() const { return m_projectName; } Core::IDocument *CMakeProject::document() const { return m_file; } CMakeManager *CMakeProject::projectManager() const { return m_manager; } ProjectExplorer::ProjectNode *CMakeProject::rootProjectNode() const { return m_rootNode; } QStringList CMakeProject::files(FilesMode fileMode) const { Q_UNUSED(fileMode) return m_files; } bool CMakeProject::fromMap(const QVariantMap &map) { if (!Project::fromMap(map)) return false; bool hasUserFile = activeTarget(); if (!hasUserFile) { CMakeOpenProjectWizard copw(Core::ICore::mainWindow(), m_manager, projectDirectory().toString(), Utils::Environment::systemEnvironment()); if (copw.exec() != QDialog::Accepted) return false; Kit *k = copw.kit(); Target *t = new Target(this, k); CMakeBuildConfiguration *bc(new CMakeBuildConfiguration(t)); bc->setDefaultDisplayName(QLatin1String("all")); bc->setUseNinja(copw.useNinja()); bc->setBuildDirectory(Utils::FileName::fromString(copw.buildDirectory())); ProjectExplorer::BuildStepList *buildSteps = bc->stepList(ProjectExplorer::Constants::BUILDSTEPS_BUILD); ProjectExplorer::BuildStepList *cleanSteps = bc->stepList(ProjectExplorer::Constants::BUILDSTEPS_CLEAN); // Now create a standard build configuration buildSteps->insertStep(0, new MakeStep(buildSteps)); MakeStep *cleanMakeStep = new MakeStep(cleanSteps); cleanSteps->insertStep(0, cleanMakeStep); cleanMakeStep->setAdditionalArguments(QLatin1String("clean")); cleanMakeStep->setClean(true); t->addBuildConfiguration(bc); t->updateDefaultDeployConfigurations(); addTarget(t); } else { // We have a user file, but we could still be missing the cbp file // or simply run createXml with the saved settings QFileInfo sourceFileInfo(m_fileName); CMakeBuildConfiguration *activeBC = qobject_cast(activeTarget()->activeBuildConfiguration()); if (!activeBC) return false; QString cbpFile = CMakeManager::findCbpFile(QDir(activeBC->buildDirectory().toString())); QFileInfo cbpFileFi(cbpFile); CMakeOpenProjectWizard::Mode mode = CMakeOpenProjectWizard::Nothing; if (!cbpFileFi.exists()) mode = CMakeOpenProjectWizard::NeedToCreate; else if (cbpFileFi.lastModified() < sourceFileInfo.lastModified()) mode = CMakeOpenProjectWizard::NeedToUpdate; if (mode != CMakeOpenProjectWizard::Nothing) { CMakeBuildInfo info(activeBC); CMakeOpenProjectWizard copw(Core::ICore::mainWindow(), m_manager, mode, &info); if (copw.exec() != QDialog::Accepted) return false; else activeBC->setUseNinja(copw.useNinja()); } } parseCMakeLists(); m_activeTarget = activeTarget(); if (m_activeTarget) connect(m_activeTarget, SIGNAL(activeBuildConfigurationChanged(ProjectExplorer::BuildConfiguration*)), this, SLOT(changeActiveBuildConfiguration(ProjectExplorer::BuildConfiguration*))); connect(this, SIGNAL(activeTargetChanged(ProjectExplorer::Target*)), this, SLOT(activeTargetWasChanged(ProjectExplorer::Target*))); return true; } bool CMakeProject::setupTarget(Target *t) { t->updateDefaultBuildConfigurations(); if (t->buildConfigurations().isEmpty()) return false; t->updateDefaultDeployConfigurations(); return true; } CMakeBuildTarget CMakeProject::buildTargetForTitle(const QString &title) { foreach (const CMakeBuildTarget &ct, m_buildTargets) if (ct.title == title) return ct; return CMakeBuildTarget(); } QString CMakeProject::uiHeaderFile(const QString &uiFile) { QFileInfo fi(uiFile); Utils::FileName project = projectDirectory(); Utils::FileName baseDirectory = Utils::FileName::fromString(fi.absolutePath()); while (baseDirectory.isChildOf(project)) { Utils::FileName cmakeListsTxt = baseDirectory; cmakeListsTxt.appendPath(QLatin1String("CMakeLists.txt")); if (cmakeListsTxt.exists()) break; QDir dir(baseDirectory.toString()); dir.cdUp(); baseDirectory = Utils::FileName::fromString(dir.absolutePath()); } QDir srcDirRoot = QDir(project.toString()); QString relativePath = srcDirRoot.relativeFilePath(baseDirectory.toString()); QDir buildDir = QDir(activeTarget()->activeBuildConfiguration()->buildDirectory().toString()); QString uiHeaderFilePath = buildDir.absoluteFilePath(relativePath); uiHeaderFilePath += QLatin1String("/ui_"); uiHeaderFilePath += fi.completeBaseName(); uiHeaderFilePath += QLatin1String(".h"); return QDir::cleanPath(uiHeaderFilePath); } void CMakeProject::updateRunConfigurations() { foreach (Target *t, targets()) updateRunConfigurations(t); } // TODO Compare with updateDefaultRunConfigurations(); void CMakeProject::updateRunConfigurations(Target *t) { // *Update* runconfigurations: QMultiMap existingRunConfigurations; QList toRemove; foreach (ProjectExplorer::RunConfiguration *rc, t->runConfigurations()) { if (CMakeRunConfiguration* cmakeRC = qobject_cast(rc)) existingRunConfigurations.insert(cmakeRC->title(), cmakeRC); QtSupport::CustomExecutableRunConfiguration *ceRC = qobject_cast(rc); if (ceRC && !ceRC->isConfigured()) toRemove << rc; } foreach (const CMakeBuildTarget &ct, buildTargets()) { if (ct.library) continue; if (ct.executable.isEmpty()) continue; QList list = existingRunConfigurations.values(ct.title); if (!list.isEmpty()) { // Already exists, so override the settings... foreach (CMakeRunConfiguration *rc, list) { rc->setExecutable(ct.executable); rc->setBaseWorkingDirectory(ct.workingDirectory); rc->setEnabled(true); } existingRunConfigurations.remove(ct.title); } else { // Does not exist yet Core::Id id = CMakeRunConfigurationFactory::idFromBuildTarget(ct.title); CMakeRunConfiguration *rc = new CMakeRunConfiguration(t, id, ct.executable, ct.workingDirectory, ct.title); t->addRunConfiguration(rc); } } QMultiMap::const_iterator it = existingRunConfigurations.constBegin(); for ( ; it != existingRunConfigurations.constEnd(); ++it) { CMakeRunConfiguration *rc = it.value(); // The executables for those runconfigurations aren't build by the current buildconfiguration // We just set a disable flag and show that in the display name rc->setEnabled(false); // removeRunConfiguration(rc); } foreach (ProjectExplorer::RunConfiguration *rc, toRemove) t->removeRunConfiguration(rc); if (t->runConfigurations().isEmpty()) { // Oh no, no run configuration, // create a custom executable run configuration t->addRunConfiguration(new QtSupport::CustomExecutableRunConfiguration(t)); } } void CMakeProject::updateApplicationAndDeploymentTargets() { Target *t = activeTarget(); QFile deploymentFile; QTextStream deploymentStream; QString deploymentPrefix; QDir sourceDir; sourceDir.setPath(t->project()->projectDirectory().toString()); deploymentFile.setFileName(sourceDir.filePath(QLatin1String("QtCreatorDeployment.txt"))); if (deploymentFile.open(QFile::ReadOnly | QFile::Text)) { deploymentStream.setDevice(&deploymentFile); deploymentPrefix = deploymentStream.readLine(); if (!deploymentPrefix.endsWith(QLatin1Char('/'))) deploymentPrefix.append(QLatin1Char('/')); } BuildTargetInfoList appTargetList; DeploymentData deploymentData; QDir buildDir(t->activeBuildConfiguration()->buildDirectory().toString()); foreach (const CMakeBuildTarget &ct, m_buildTargets) { if (ct.executable.isEmpty()) continue; deploymentData.addFile(ct.executable, deploymentPrefix + buildDir.relativeFilePath(QFileInfo(ct.executable).dir().path()), DeployableFile::TypeExecutable); if (!ct.library) { // TODO: Put a path to corresponding .cbp file into projectFilePath? appTargetList.list << BuildTargetInfo(ct.title, Utils::FileName::fromString(ct.executable), Utils::FileName::fromString(ct.executable)); } } QString absoluteSourcePath = sourceDir.absolutePath(); if (!absoluteSourcePath.endsWith(QLatin1Char('/'))) absoluteSourcePath.append(QLatin1Char('/')); if (deploymentStream.device()) { while (!deploymentStream.atEnd()) { QString line = deploymentStream.readLine(); if (!line.contains(QLatin1Char(':'))) continue; QStringList file = line.split(QLatin1Char(':')); deploymentData.addFile(absoluteSourcePath + file.at(0), deploymentPrefix + file.at(1)); } } t->setApplicationTargets(appTargetList); t->setDeploymentData(deploymentData); } void CMakeProject::createUiCodeModelSupport() { QHash uiFileHash; // Find all ui files foreach (const QString &uiFile, m_files) { if (uiFile.endsWith(QLatin1String(".ui"))) uiFileHash.insert(uiFile, uiHeaderFile(uiFile)); } QtSupport::UiCodeModelManager::update(this, uiFileHash); } // CMakeFile CMakeFile::CMakeFile(CMakeProject *parent, QString fileName) : Core::IDocument(parent), m_project(parent) { setId("Cmake.ProjectFile"); setMimeType(QLatin1String(Constants::CMAKEPROJECTMIMETYPE)); setFilePath(fileName); } bool CMakeFile::save(QString *errorString, const QString &fileName, bool autoSave) { // Once we have an texteditor open for this file, we probably do // need to implement this, don't we. Q_UNUSED(errorString) Q_UNUSED(fileName) Q_UNUSED(autoSave) return false; } QString CMakeFile::defaultPath() const { return QString(); } QString CMakeFile::suggestedFileName() const { return QString(); } bool CMakeFile::isModified() const { return false; } bool CMakeFile::isSaveAsAllowed() const { return false; } Core::IDocument::ReloadBehavior CMakeFile::reloadBehavior(ChangeTrigger state, ChangeType type) const { Q_UNUSED(state) Q_UNUSED(type) return BehaviorSilent; } bool CMakeFile::reload(QString *errorString, ReloadFlag flag, ChangeType type) { Q_UNUSED(errorString) Q_UNUSED(flag) Q_UNUSED(type) return true; } CMakeBuildSettingsWidget::CMakeBuildSettingsWidget(CMakeBuildConfiguration *bc) : m_buildConfiguration(0) { QFormLayout *fl = new QFormLayout(this); fl->setContentsMargins(20, -1, 0, -1); fl->setFieldGrowthPolicy(QFormLayout::ExpandingFieldsGrow); setLayout(fl); QPushButton *runCmakeButton = new QPushButton(tr("Run CMake...")); connect(runCmakeButton, SIGNAL(clicked()), this, SLOT(runCMake())); fl->addRow(tr("Reconfigure project:"), runCmakeButton); m_pathLineEdit = new QLineEdit(this); m_pathLineEdit->setReadOnly(true); QHBoxLayout *hbox = new QHBoxLayout(); hbox->addWidget(m_pathLineEdit); m_changeButton = new QPushButton(this); m_changeButton->setText(tr("&Change")); connect(m_changeButton, SIGNAL(clicked()), this, SLOT(openChangeBuildDirectoryDialog())); hbox->addWidget(m_changeButton); fl->addRow(tr("Build directory:"), hbox); m_buildConfiguration = bc; m_pathLineEdit->setText(m_buildConfiguration->rawBuildDirectory().toString()); if (m_buildConfiguration->buildDirectory() == bc->target()->project()->projectDirectory()) m_changeButton->setEnabled(false); else m_changeButton->setEnabled(true); setDisplayName(tr("CMake")); } void CMakeBuildSettingsWidget::openChangeBuildDirectoryDialog() { CMakeProject *project = static_cast(m_buildConfiguration->target()->project()); CMakeBuildInfo info(m_buildConfiguration); CMakeOpenProjectWizard copw(Core::ICore::mainWindow(), project->projectManager(), CMakeOpenProjectWizard::ChangeDirectory, &info); if (copw.exec() == QDialog::Accepted) { project->changeBuildDirectory(m_buildConfiguration, copw.buildDirectory()); m_buildConfiguration->setUseNinja(copw.useNinja()); m_pathLineEdit->setText(m_buildConfiguration->rawBuildDirectory().toString()); } } void CMakeBuildSettingsWidget::runCMake() { if (!ProjectExplorer::ProjectExplorerPlugin::saveModifiedFiles()) return; CMakeProject *project = static_cast(m_buildConfiguration->target()->project()); CMakeBuildInfo info(m_buildConfiguration); CMakeOpenProjectWizard copw(Core::ICore::mainWindow(), project->projectManager(), CMakeOpenProjectWizard::WantToUpdate, &info); if (copw.exec() == QDialog::Accepted) project->parseCMakeLists(); } ///// // CMakeCbpParser //// namespace { int distance(const QString &targetDirectory, const Utils::FileName &fileName) { const QString commonParent = Utils::commonPath(QStringList() << targetDirectory << fileName.toString()); return targetDirectory.mid(commonParent.size()).count(QLatin1Char('/')) + fileName.toString().mid(commonParent.size()).count(QLatin1Char('/')); } } // called after everything is parsed // this function tries to figure out to which CMakeBuildTarget // each file belongs, so that it gets the appropriate defines and // compiler flags void CMakeCbpParser::sortFiles() { QLoggingCategory log("qtc.cmakeprojectmanager.filetargetmapping"); QList fileNames = Utils::transform(m_fileList, [] (FileNode *node) { return Utils::FileName::fromString(node->path()); }); Utils::sort(fileNames); CMakeBuildTarget *last = 0; Utils::FileName parentDirectory; qCDebug(log) << "###############"; qCDebug(log) << "# Pre Dump #"; qCDebug(log) << "###############"; foreach (const CMakeBuildTarget &target, m_buildTargets) qCDebug(log) << target.title << target.sourceDirectory << target.includeFiles << target.defines << target.files << "\n"; // find a good build target to fall back int fallbackIndex = 0; { int bestIncludeCount = -1; for (int i = 0; i < m_buildTargets.size(); ++i) { const CMakeBuildTarget &target = m_buildTargets.at(i); if (target.includeFiles.isEmpty()) continue; if (target.sourceDirectory == m_sourceDirectory && target.includeFiles.count() > bestIncludeCount) { bestIncludeCount = target.includeFiles.count(); fallbackIndex = i; } } } qCDebug(log) << "###############"; qCDebug(log) << "# Sorting #"; qCDebug(log) << "###############"; foreach (const Utils::FileName &fileName, fileNames) { qCDebug(log) << fileName; if (fileName.parentDir() == parentDirectory && last) { // easy case, same parent directory as last file last->files.append(fileName.toString()); qCDebug(log) << " into" << last->title; } else { int bestDistance = std::numeric_limits::max(); int bestIndex = -1; int bestIncludeCount = -1; for (int i = 0; i < m_buildTargets.size(); ++i) { const CMakeBuildTarget &target = m_buildTargets.at(i); if (target.includeFiles.isEmpty()) continue; int dist = distance(target.sourceDirectory, fileName); qCDebug(log) << "distance to target" << target.title << dist; if (dist < bestDistance || (dist == bestDistance && target.includeFiles.count() > bestIncludeCount)) { bestDistance = dist; bestIncludeCount = target.includeFiles.count(); bestIndex = i; } } if (bestIndex == -1 && !m_buildTargets.isEmpty()) { bestIndex = fallbackIndex; qCDebug(log) << " using fallbackIndex"; } if (bestIndex != -1) { m_buildTargets[bestIndex].files.append(fileName.toString()); last = &m_buildTargets[bestIndex]; parentDirectory = fileName.parentDir(); qCDebug(log) << " into" << last->title; } } } qCDebug(log) << "###############"; qCDebug(log) << "# After Dump #"; qCDebug(log) << "###############"; foreach (const CMakeBuildTarget &target, m_buildTargets) qCDebug(log) << target.title << target.sourceDirectory << target.includeFiles << target.defines << target.files << "\n"; } bool CMakeCbpParser::parseCbpFile(const QString &fileName, const QString &sourceDirectory) { m_buildDirectory = QFileInfo(fileName).absolutePath(); m_sourceDirectory = sourceDirectory; QFile fi(fileName); if (fi.exists() && fi.open(QFile::ReadOnly)) { setDevice(&fi); while (!atEnd()) { readNext(); if (name() == QLatin1String("CodeBlocks_project_file")) parseCodeBlocks_project_file(); else if (isStartElement()) parseUnknownElement(); } sortFiles(); fi.close(); return true; } return false; } void CMakeCbpParser::parseCodeBlocks_project_file() { while (!atEnd()) { readNext(); if (isEndElement()) return; else if (name() == QLatin1String("Project")) parseProject(); else if (isStartElement()) parseUnknownElement(); } } void CMakeCbpParser::parseProject() { while (!atEnd()) { readNext(); if (isEndElement()) return; else if (name() == QLatin1String("Option")) parseOption(); else if (name() == QLatin1String("Unit")) parseUnit(); else if (name() == QLatin1String("Build")) parseBuild(); else if (isStartElement()) parseUnknownElement(); } } void CMakeCbpParser::parseBuild() { while (!atEnd()) { readNext(); if (isEndElement()) return; else if (name() == QLatin1String("Target")) parseBuildTarget(); else if (isStartElement()) parseUnknownElement(); } } void CMakeCbpParser::parseBuildTarget() { m_buildTarget.clear(); if (attributes().hasAttribute(QLatin1String("title"))) m_buildTarget.title = attributes().value(QLatin1String("title")).toString(); while (!atEnd()) { readNext(); if (isEndElement()) { if (!m_buildTarget.title.endsWith(QLatin1String("/fast"))) m_buildTargets.append(m_buildTarget); return; } else if (name() == QLatin1String("Compiler")) { parseCompiler(); } else if (name() == QLatin1String("Option")) { parseBuildTargetOption(); } else if (name() == QLatin1String("MakeCommands")) { parseMakeCommands(); } else if (isStartElement()) { parseUnknownElement(); } } } void CMakeCbpParser::parseBuildTargetOption() { if (attributes().hasAttribute(QLatin1String("output"))) { m_buildTarget.executable = attributes().value(QLatin1String("output")).toString(); } else if (attributes().hasAttribute(QLatin1String("type"))) { const QStringRef value = attributes().value(QLatin1String("type")); if (value == QLatin1String("2") || value == QLatin1String("3")) m_buildTarget.library = true; } else if (attributes().hasAttribute(QLatin1String("working_dir"))) { m_buildTarget.workingDirectory = attributes().value(QLatin1String("working_dir")).toString(); QDir dir(m_buildDirectory); QString relative = dir.relativeFilePath(m_buildTarget.workingDirectory); m_buildTarget.sourceDirectory = Utils::FileName::fromString(m_sourceDirectory).appendPath(relative).toString(); } while (!atEnd()) { readNext(); if (isEndElement()) return; else if (isStartElement()) parseUnknownElement(); } } QString CMakeCbpParser::projectName() const { return m_projectName; } void CMakeCbpParser::parseOption() { if (attributes().hasAttribute(QLatin1String("title"))) m_projectName = attributes().value(QLatin1String("title")).toString(); if (attributes().hasAttribute(QLatin1String("compiler"))) m_compiler = attributes().value(QLatin1String("compiler")).toString(); while (!atEnd()) { readNext(); if (isEndElement()) return; else if (isStartElement()) parseUnknownElement(); } } void CMakeCbpParser::parseMakeCommands() { while (!atEnd()) { readNext(); if (isEndElement()) return; else if (name() == QLatin1String("Build")) parseBuildTargetBuild(); else if (name() == QLatin1String("Clean")) parseBuildTargetClean(); else if (isStartElement()) parseUnknownElement(); } } void CMakeCbpParser::parseBuildTargetBuild() { if (attributes().hasAttribute(QLatin1String("command"))) m_buildTarget.makeCommand = attributes().value(QLatin1String("command")).toString(); while (!atEnd()) { readNext(); if (isEndElement()) return; else if (isStartElement()) parseUnknownElement(); } } void CMakeCbpParser::parseBuildTargetClean() { if (attributes().hasAttribute(QLatin1String("command"))) m_buildTarget.makeCleanCommand = attributes().value(QLatin1String("command")).toString(); while (!atEnd()) { readNext(); if (isEndElement()) return; else if (isStartElement()) parseUnknownElement(); } } void CMakeCbpParser::parseCompiler() { while (!atEnd()) { readNext(); if (isEndElement()) return; else if (name() == QLatin1String("Add")) parseAdd(); else if (isStartElement()) parseUnknownElement(); } } void CMakeCbpParser::parseAdd() { // CMake only supports and const QXmlStreamAttributes addAttributes = attributes(); const QString includeDirectory = addAttributes.value(QLatin1String("directory")).toString(); // allow adding multiple times because order happens if (!includeDirectory.isEmpty()) m_buildTarget.includeFiles.append(includeDirectory); QString compilerOption = addAttributes.value(QLatin1String("option")).toString(); // defining multiple times a macro to the same value makes no sense if (!compilerOption.isEmpty() && !m_buildTarget.compilerOptions.contains(compilerOption)) { m_buildTarget.compilerOptions.append(compilerOption); int macroNameIndex = compilerOption.indexOf(QLatin1String("-D")) + 2; if (macroNameIndex != 1) { int assignIndex = compilerOption.indexOf(QLatin1Char('='), macroNameIndex); if (assignIndex != -1) compilerOption[assignIndex] = ' '; m_buildTarget.defines.append("#define "); m_buildTarget.defines.append(compilerOption.mid(macroNameIndex).toUtf8()); m_buildTarget.defines.append('\n'); } } while (!atEnd()) { readNext(); if (isEndElement()) return; else if (isStartElement()) parseUnknownElement(); } } void CMakeCbpParser::parseUnit() { //qDebug()< CMakeCbpParser::fileList() { return m_fileList; } QList CMakeCbpParser::cmakeFileList() { return m_cmakeFileList; } bool CMakeCbpParser::hasCMakeFiles() { return !m_cmakeFileList.isEmpty(); } QList CMakeCbpParser::buildTargets() { return m_buildTargets; } QString CMakeCbpParser::compilerName() const { return m_compiler; } void CMakeBuildTarget::clear() { executable.clear(); makeCommand.clear(); makeCleanCommand.clear(); workingDirectory.clear(); sourceDirectory.clear(); title.clear(); library = false; includeFiles.clear(); compilerOptions.clear(); defines.clear(); }