diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.cpp b/src/plugins/cmakeprojectmanager/cmakeproject.cpp index f6227b46591ee06664b83fd362e06b45460c60d9..55af5a0d8d4a4d5a4120bc5b20672d4969a72f7c 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.cpp +++ b/src/plugins/cmakeprojectmanager/cmakeproject.cpp @@ -190,6 +190,55 @@ QString CMakeProject::shadowBuildDirectory(const QString &projectFilePath, const return QDir::cleanPath(projectDir.absoluteFilePath(buildPath)); } +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() || @@ -215,7 +264,7 @@ bool CMakeProject::parseCMakeLists() CMakeCbpParser cbpparser; // Parsing //qDebug()<<"Parsing file "<<cbpFile; - if (!cbpparser.parseCbpFile(cbpFile)) { + if (!cbpparser.parseCbpFile(cbpFile, projectDirectory().toString())) { // TODO report error emit buildTargetsChanged(); return false; @@ -277,111 +326,61 @@ bool CMakeProject::parseCMakeLists() return true; } - QStringList cxxflags; - bool found = false; - foreach (const CMakeBuildTarget &buildTarget, m_buildTargets) { - QString makeCommand = QDir::fromNativeSeparators(buildTarget.makeCommand); - int startIndex = makeCommand.indexOf(QLatin1Char('\"')); - int endIndex = makeCommand.indexOf(QLatin1Char('\"'), startIndex + 1); - if (startIndex == -1 || endIndex == -1) - continue; - 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 = - cxxflags = line.mid(11).trimmed().split(QLatin1Char(' '), QString::SkipEmptyParts); - found = true; - break; - } - } - } - if (found) - break; - } - // Attempt to find build.ninja file and obtain FLAGS (CXX_FLAGS) from there if no suitable flags.make were - // found - if (!found && !cbpparser.buildTargets().isEmpty()) { - // Get "all" target's working directory - QString buildNinjaFile = QDir::fromNativeSeparators(cbpparser.buildTargets().at(0).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 = - cxxflags = line.mid(7).trimmed().split(QLatin1Char(' '), QString::SkipEmptyParts); - break; - } - } - } - } - CppTools::CppModelManagerInterface *modelmanager = CppTools::CppModelManagerInterface::instance(); if (modelmanager) { CppTools::CppModelManagerInterface::ProjectInfo pinfo = modelmanager->projectInfo(this); pinfo.clearProjectParts(); - typedef CppTools::ProjectPart ProjectPart; - ProjectPart::Ptr part(new ProjectPart); - part->project = this; - part->displayName = displayName(); - part->projectFile = projectFilePath().toString(); - - // This explicitly adds -I. to the include paths - part->headerPaths += ProjectPart::HeaderPath(projectDirectory().toString(), - ProjectPart::HeaderPath::IncludePath); - - foreach (const QString &includeFile, cbpparser.includeFiles()) { - ProjectPart::HeaderPath hp(includeFile, ProjectPart::HeaderPath::IncludePath); - - // CodeBlocks is utterly ignorant of frameworks on Mac, and won't report framework - // paths. The work-around is to check if the include path ends in ".framework", and - // if so, add the parent directory as framework path. - if (includeFile.endsWith(QLatin1String(".framework"))) { - const int slashIdx = includeFile.lastIndexOf(QLatin1Char('/')); - if (slashIdx != -1) { - hp = ProjectPart::HeaderPath(includeFile.left(slashIdx), - ProjectPart::HeaderPath::FrameworkPath); - continue; + foreach (const CMakeBuildTarget &cbt, m_buildTargets) { + typedef CppTools::ProjectPart ProjectPart; + ProjectPart::Ptr part(new ProjectPart); + part->project = this; + part->displayName = cbt.title; + part->projectFile = projectFilePath().toString(); + + // This explicitly adds -I. to the include paths + part->headerPaths += ProjectPart::HeaderPath(projectDirectory().toString(), + ProjectPart::HeaderPath::IncludePath); + + foreach (const QString &includeFile, cbt.includeFiles) { + ProjectPart::HeaderPath hp(includeFile, ProjectPart::HeaderPath::IncludePath); + + // CodeBlocks is utterly ignorant of frameworks on Mac, and won't report framework + // paths. The work-around is to check if the include path ends in ".framework", and + // if so, add the parent directory as framework path. + if (includeFile.endsWith(QLatin1String(".framework"))) { + const int slashIdx = includeFile.lastIndexOf(QLatin1Char('/')); + if (slashIdx != -1) { + hp = ProjectPart::HeaderPath(includeFile.left(slashIdx), + ProjectPart::HeaderPath::FrameworkPath); + continue; + } } + + part->headerPaths += hp; } - part->headerPaths += hp; - } + part->projectDefines += cbt.defines; - part->projectDefines += cbpparser.defines(); + // TODO rewrite + CppTools::ProjectFileAdder adder(part->files); + foreach (const QString &file, cbt.files) + adder.maybeAdd(file); - CppTools::ProjectFileAdder adder(part->files); - foreach (const QString &file, m_files) - adder.maybeAdd(file); + QStringList cxxflags = getCXXFlagsFor(cbt); - part->evaluateToolchain(tc, - cxxflags, - cxxflags, - SysRootKitInformation::sysRoot(k)); + part->evaluateToolchain(tc, + cxxflags, + cxxflags, + SysRootKitInformation::sysRoot(k)); - pinfo.appendProjectPart(part); + setProjectLanguage(ProjectExplorer::Constants::LANG_CXX, !part->files.isEmpty()); + pinfo.appendProjectPart(part); + } m_codeModelFuture.cancel(); m_codeModelFuture = modelmanager->updateProjectInfo(pinfo); - setProjectLanguage(ProjectExplorer::Constants::LANG_CXX, !part->files.isEmpty()); } emit buildTargetsChanged(); @@ -890,8 +889,56 @@ void CMakeBuildSettingsWidget::runCMake() // CMakeCbpParser //// -bool CMakeCbpParser::parseCbpFile(const QString &fileName) +// 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() { + QList<Utils::FileName> fileNames = Utils::transform(m_fileList, [] (FileNode *node) { + return Utils::FileName::fromString(node->path()); + }); + + Utils::sort(fileNames); + + + CMakeBuildTarget *last = 0; + Utils::FileName parentDirectory; + + foreach (const Utils::FileName &fileName, fileNames) { + if (fileName.parentDir() == parentDirectory && last) { + // easy case, same parent directory as last file + last->files.append(fileName.toString()); + } else { + int bestLength = -1; + int bestIndex = -1; + + for (int i = 0; i < m_buildTargets.size(); ++i) { + const CMakeBuildTarget &target = m_buildTargets.at(i); + if (fileName.isChildOf(Utils::FileName::fromString(target.sourceDirectory)) + && target.sourceDirectory.size() > bestLength) { + bestLength = target.sourceDirectory.size(); + bestIndex = i; + } + } + + if (bestIndex == -1 && !m_buildTargets.isEmpty()) + bestIndex = 0; + + if (bestIndex != -1) { + m_buildTargets[bestIndex].files.append(fileName.toString()); + last = &m_buildTargets[bestIndex]; + parentDirectory = fileName.parentDir(); + } + } + } +} + +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); @@ -903,9 +950,10 @@ bool CMakeCbpParser::parseCbpFile(const QString &fileName) else if (isStartElement()) parseUnknownElement(); } + + sortFiles(); + fi.close(); - m_includeFiles.sort(); - m_includeFiles.removeDuplicates(); return true; } return false; @@ -988,6 +1036,10 @@ void CMakeCbpParser::parseBuildTargetOption() 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(); @@ -1082,20 +1134,20 @@ void CMakeCbpParser::parseAdd() const QString includeDirectory = addAttributes.value(QLatin1String("directory")).toString(); // allow adding multiple times because order happens if (!includeDirectory.isEmpty()) - m_includeFiles.append(includeDirectory); + 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_compilerOptions.contains(compilerOption)) { - m_compilerOptions.append(compilerOption); + 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_defines.append("#define "); - m_defines.append(compilerOption.mid(macroNameIndex).toUtf8()); - m_defines.append('\n'); + m_buildTarget.defines.append("#define "); + m_buildTarget.defines.append(compilerOption.mid(macroNameIndex).toUtf8()); + m_buildTarget.defines.append('\n'); } } @@ -1190,16 +1242,6 @@ bool CMakeCbpParser::hasCMakeFiles() return !m_cmakeFileList.isEmpty(); } -QStringList CMakeCbpParser::includeFiles() -{ - return m_includeFiles; -} - -QByteArray CMakeCbpParser::defines() const -{ - return m_defines; -} - QList<CMakeBuildTarget> CMakeCbpParser::buildTargets() { return m_buildTargets; @@ -1216,6 +1258,10 @@ void CMakeBuildTarget::clear() makeCommand.clear(); makeCleanCommand.clear(); workingDirectory.clear(); + sourceDirectory.clear(); title.clear(); library = false; + includeFiles.clear(); + compilerOptions.clear(); + defines.clear(); } diff --git a/src/plugins/cmakeprojectmanager/cmakeproject.h b/src/plugins/cmakeprojectmanager/cmakeproject.h index cd0edf866f114db0b6795bc0e43e121ab0baab27..aa97f99e0842ac65e2722b9f27185bea23841c26 100644 --- a/src/plugins/cmakeprojectmanager/cmakeproject.h +++ b/src/plugins/cmakeprojectmanager/cmakeproject.h @@ -65,8 +65,16 @@ struct CMakeBuildTarget QString executable; // TODO: rename to output? bool library; QString workingDirectory; + QString sourceDirectory; QString makeCommand; QString makeCleanCommand; + + // code model + QStringList includeFiles; + QStringList compilerOptions; + QByteArray defines; + QStringList files; + void clear(); }; @@ -125,6 +133,7 @@ private: QString uiHeaderFile(const QString &uiFile); void updateRunConfigurations(ProjectExplorer::Target *t); void updateApplicationAndDeploymentTargets(); + QStringList getCXXFlagsFor(const CMakeBuildTarget &buildTarget); CMakeManager *m_manager; ProjectExplorer::Target *m_activeTarget; @@ -144,12 +153,10 @@ private: class CMakeCbpParser : public QXmlStreamReader { public: - bool parseCbpFile(const QString &fileName); + bool parseCbpFile(const QString &fileName, const QString &sourceDirectory); QList<ProjectExplorer::FileNode *> fileList(); QList<ProjectExplorer::FileNode *> cmakeFileList(); - QStringList includeFiles(); QList<CMakeBuildTarget> buildTargets(); - QByteArray defines() const; QString projectName() const; QString compilerName() const; bool hasCMakeFiles(); @@ -169,19 +176,19 @@ private: void parseUnit(); void parseUnitOption(); void parseUnknownElement(); + void sortFiles(); QList<ProjectExplorer::FileNode *> m_fileList; QList<ProjectExplorer::FileNode *> m_cmakeFileList; QSet<QString> m_processedUnits; bool m_parsingCmakeUnit; - QStringList m_includeFiles; - QStringList m_compilerOptions; - QByteArray m_defines; CMakeBuildTarget m_buildTarget; QList<CMakeBuildTarget> m_buildTargets; QString m_projectName; QString m_compiler; + QString m_sourceDirectory; + QString m_buildDirectory; }; class CMakeFile : public Core::IDocument