CppTools: Classify ambiguous headers depending on other files

This applies for all project managers, except qmake. The qmake project
manager will make use of this in follow up changes.

Before, "foo.h" was always recognized as a CXXHeader. Now, it depends on
the other files. E.g. in a file list {"foo.h", "foo.c"} foo.h is now a
CHeader. In {"foo.h", "foo.c", "bar.cpp"} the file "foo.h" is ambiguous
and we will create two project parts, one where it is a CHeader, the
other where it is a CXXHeader.

Change-Id: I50505163368742584b1380c284d42cbe07cb4fc9
Reviewed-by: David Schulz <david.schulz@qt.io>
This commit is contained in:
Nikolai Kosjar
2016-11-29 17:32:34 +01:00
parent f54d4fc3db
commit 3a369552c6
12 changed files with 328 additions and 42 deletions

View File

@@ -166,6 +166,7 @@ QString Utils::toString(ProjectFile::Kind kind)
CASE_PROFECTFILEKIND(ObjCXXSource); CASE_PROFECTFILEKIND(ObjCXXSource);
CASE_PROFECTFILEKIND(CudaSource); CASE_PROFECTFILEKIND(CudaSource);
CASE_PROFECTFILEKIND(OpenCLSource); CASE_PROFECTFILEKIND(OpenCLSource);
CASE_PROFECTFILEKIND(AmbiguousHeader);
// no default to get a compiler warning if anything is added // no default to get a compiler warning if anything is added
} }
#undef CASE_PROFECTFILEKIND #undef CASE_PROFECTFILEKIND

View File

@@ -41,6 +41,9 @@ ProjectFile::ProjectFile(const QString &filePath, Kind kind)
ProjectFile::Kind ProjectFile::classify(const QString &filePath) ProjectFile::Kind ProjectFile::classify(const QString &filePath)
{ {
if (isAmbiguousHeader(filePath))
return AmbiguousHeader;
Utils::MimeDatabase mdb; Utils::MimeDatabase mdb;
const Utils::MimeType mimeType = mdb.mimeTypeForFile(filePath); const Utils::MimeType mimeType = mdb.mimeTypeForFile(filePath);
if (!mimeType.isValid()) if (!mimeType.isValid())
@@ -65,6 +68,11 @@ ProjectFile::Kind ProjectFile::classify(const QString &filePath)
return Unclassified; return Unclassified;
} }
bool ProjectFile::isAmbiguousHeader(const QString &filePath)
{
return filePath.endsWith(".h");
}
bool ProjectFile::isHeader(ProjectFile::Kind kind) bool ProjectFile::isHeader(ProjectFile::Kind kind)
{ {
switch (kind) { switch (kind) {
@@ -73,6 +81,7 @@ bool ProjectFile::isHeader(ProjectFile::Kind kind)
case ProjectFile::ObjCHeader: case ProjectFile::ObjCHeader:
case ProjectFile::ObjCXXHeader: case ProjectFile::ObjCXXHeader:
case ProjectFile::Unclassified: // no file extension, e.g. stl headers case ProjectFile::Unclassified: // no file extension, e.g. stl headers
case ProjectFile::AmbiguousHeader:
return true; return true;
default: default:
return false; return false;
@@ -99,6 +108,7 @@ static const char *projectFileKindToText(ProjectFile::Kind kind)
{ {
switch (kind) { switch (kind) {
RETURN_TEXT_FOR_CASE(Unclassified); RETURN_TEXT_FOR_CASE(Unclassified);
RETURN_TEXT_FOR_CASE(AmbiguousHeader);
RETURN_TEXT_FOR_CASE(CHeader); RETURN_TEXT_FOR_CASE(CHeader);
RETURN_TEXT_FOR_CASE(CSource); RETURN_TEXT_FOR_CASE(CSource);
RETURN_TEXT_FOR_CASE(CXXHeader); RETURN_TEXT_FOR_CASE(CXXHeader);

View File

@@ -36,6 +36,7 @@ class CPPTOOLS_EXPORT ProjectFile
public: public:
enum Kind { enum Kind {
Unclassified, Unclassified,
AmbiguousHeader,
CHeader, CHeader,
CSource, CSource,
CXXHeader, CXXHeader,
@@ -49,8 +50,10 @@ public:
}; };
static Kind classify(const QString &filePath); static Kind classify(const QString &filePath);
static bool isHeader(Kind kind);
static bool isSource(Kind kind); static bool isSource(Kind kind);
static bool isHeader(Kind kind);
static bool isAmbiguousHeader(const QString &filePath);
public: public:
ProjectFile() = default; ProjectFile() = default;

View File

@@ -32,42 +32,8 @@ ProjectFileCategorizer::ProjectFileCategorizer(const QString &projectPartName,
ProjectPartBuilder::FileClassifier fileClassifier) ProjectPartBuilder::FileClassifier fileClassifier)
: m_partName(projectPartName) : m_partName(projectPartName)
{ {
ProjectFiles cHeaders; const QStringList ambiguousHeaders = classifyFiles(filePaths, fileClassifier);
ProjectFiles cxxHeaders; expandSourcesWithAmbiguousHeaders(ambiguousHeaders);
foreach (const QString &filePath, filePaths) {
const ProjectFile::Kind kind = fileClassifier
? fileClassifier(filePath)
: ProjectFile::classify(filePath);
const ProjectFile projectFile(filePath, kind);
switch (kind) {
case ProjectFile::CSource: m_cSources += projectFile; break;
case ProjectFile::CHeader: cHeaders += projectFile; break;
case ProjectFile::CXXSource: m_cxxSources += projectFile; break;
case ProjectFile::CXXHeader: cxxHeaders += projectFile; break;
case ProjectFile::ObjCSource: m_objcSources += projectFile; break;
case ProjectFile::ObjCXXSource: m_objcxxSources += projectFile; break;
default:
continue;
}
}
const bool hasC = !m_cSources.isEmpty();
const bool hasCxx = !m_cxxSources.isEmpty();
const bool hasObjc = !m_objcSources.isEmpty();
const bool hasObjcxx = !m_objcxxSources.isEmpty();
if (hasObjcxx)
m_objcxxSources += cxxHeaders + cHeaders;
if (hasCxx)
m_cxxSources += cxxHeaders + cHeaders;
else if (!hasObjcxx)
m_cxxSources += cxxHeaders;
if (hasObjc)
m_objcSources += cHeaders;
if (hasC || (!hasObjc && !hasObjcxx && !hasCxx))
m_cSources += cHeaders;
m_partCount = (m_cSources.isEmpty() ? 0 : 1) m_partCount = (m_cSources.isEmpty() ? 0 : 1)
+ (m_cxxSources.isEmpty() ? 0 : 1) + (m_cxxSources.isEmpty() ? 0 : 1)
@@ -83,4 +49,81 @@ QString ProjectFileCategorizer::partName(const QString &languageName) const
return m_partName; return m_partName;
} }
QStringList ProjectFileCategorizer::classifyFiles(
const QStringList &filePaths,
ProjectPartBuilder::FileClassifier fileClassifier)
{
QStringList ambiguousHeaders;
foreach (const QString &filePath, filePaths) {
const ProjectFile::Kind kind = fileClassifier
? fileClassifier(filePath)
: ProjectFile::classify(filePath);
switch (kind) {
case ProjectFile::AmbiguousHeader:
ambiguousHeaders += filePath;
break;
case ProjectFile::CXXSource:
case ProjectFile::CXXHeader:
m_cxxSources += ProjectFile(filePath, kind);
break;
case ProjectFile::ObjCXXSource:
case ProjectFile::ObjCXXHeader:
m_objcxxSources += ProjectFile(filePath, kind);
break;
case ProjectFile::CSource:
case ProjectFile::CHeader:
m_cSources += ProjectFile(filePath, kind);
break;
case ProjectFile::ObjCSource:
case ProjectFile::ObjCHeader:
m_objcSources += ProjectFile(filePath, kind);
break;
default:
continue;
}
}
return ambiguousHeaders;
}
static ProjectFiles toProjectFilesWithKind(const QStringList &filePaths,
const ProjectFile::Kind kind)
{
ProjectFiles projectFiles;
projectFiles.reserve(filePaths.size());
foreach (const QString &filePath, filePaths)
projectFiles += ProjectFile(filePath, kind);
return projectFiles;
}
void ProjectFileCategorizer::expandSourcesWithAmbiguousHeaders(const QStringList &ambiguousHeaders)
{
const bool hasC = !m_cSources.isEmpty();
const bool hasCxx = !m_cxxSources.isEmpty();
const bool hasObjc = !m_objcSources.isEmpty();
const bool hasObjcxx = !m_objcxxSources.isEmpty();
const bool hasOnlyAmbiguousHeaders
= !hasC
&& !hasCxx
&& !hasObjc
&& !hasObjcxx
&& !ambiguousHeaders.isEmpty();
if (hasC || hasOnlyAmbiguousHeaders)
m_cSources += toProjectFilesWithKind(ambiguousHeaders, ProjectFile::CHeader);
if (hasCxx || hasOnlyAmbiguousHeaders)
m_cxxSources += toProjectFilesWithKind(ambiguousHeaders, ProjectFile::CXXHeader);
if (hasObjc || hasOnlyAmbiguousHeaders)
m_objcSources += toProjectFilesWithKind(ambiguousHeaders, ProjectFile::ObjCHeader);
if (hasObjcxx || hasOnlyAmbiguousHeaders)
m_objcxxSources += toProjectFilesWithKind(ambiguousHeaders, ProjectFile::ObjCXXHeader);
}
} // namespace CppTools } // namespace CppTools

View File

@@ -35,11 +35,13 @@ namespace CppTools {
class ProjectFileCategorizer class ProjectFileCategorizer
{ {
public:
using FileClassifier = ProjectPartBuilder::FileClassifier;
public: public:
ProjectFileCategorizer(const QString &projectPartName, ProjectFileCategorizer(const QString &projectPartName,
const QStringList &filePaths, const QStringList &filePaths,
ProjectPartBuilder::FileClassifier fileClassifier FileClassifier fileClassifier = FileClassifier());
= ProjectPartBuilder::FileClassifier());
bool hasCSources() const { return !m_cSources.isEmpty(); } bool hasCSources() const { return !m_cSources.isEmpty(); }
bool hasCxxSources() const { return !m_cxxSources.isEmpty(); } bool hasCxxSources() const { return !m_cxxSources.isEmpty(); }
@@ -56,6 +58,10 @@ public:
QString partName(const QString &languageName) const; QString partName(const QString &languageName) const;
private:
QStringList classifyFiles(const QStringList &filePaths, FileClassifier fileClassifier);
void expandSourcesWithAmbiguousHeaders(const QStringList &ambiguousHeaders);
private: private:
QString m_partName; QString m_partName;
ProjectFiles m_cSources; ProjectFiles m_cSources;

View File

@@ -279,7 +279,7 @@ static QStringList matchingCandidateSuffixes(ProjectFile::Kind kind)
{ {
Utils::MimeDatabase mdb; Utils::MimeDatabase mdb;
switch (kind) { switch (kind) {
// Note that C/C++ headers are undistinguishable case ProjectFile::AmbiguousHeader:
case ProjectFile::CHeader: case ProjectFile::CHeader:
case ProjectFile::CXXHeader: case ProjectFile::CXXHeader:
case ProjectFile::ObjCHeader: case ProjectFile::ObjCHeader:

View File

@@ -8,10 +8,12 @@ HEADERS += \
$$PWD/cppprojectfile.h \ $$PWD/cppprojectfile.h \
$$PWD/senddocumenttracker.h \ $$PWD/senddocumenttracker.h \
$$PWD/projectpart.h \ $$PWD/projectpart.h \
$$PWD/compileroptionsbuilder.h $$PWD/compileroptionsbuilder.h \
$$PWD/cppprojectfilecategorizer.h
SOURCES += \ SOURCES += \
$$PWD/cppprojectfile.cpp \ $$PWD/cppprojectfile.cpp \
$$PWD/senddocumenttracker.cpp \ $$PWD/senddocumenttracker.cpp \
$$PWD/projectpart.cpp \ $$PWD/projectpart.cpp \
$$PWD/compileroptionsbuilder.cpp $$PWD/compileroptionsbuilder.cpp \
$$PWD/cppprojectfilecategorizer.cpp

View File

@@ -385,6 +385,7 @@ void ComponentViewController::createComponentModel(const ProjectExplorer::Folder
bool isSource = false; bool isSource = false;
CppTools::ProjectFile::Kind kind = CppTools::ProjectFile::classify(fileNode->filePath().toString()); CppTools::ProjectFile::Kind kind = CppTools::ProjectFile::classify(fileNode->filePath().toString());
switch (kind) { switch (kind) {
case CppTools::ProjectFile::AmbiguousHeader:
case CppTools::ProjectFile::CHeader: case CppTools::ProjectFile::CHeader:
case CppTools::ProjectFile::CSource: case CppTools::ProjectFile::CSource:
case CppTools::ProjectFile::CXXHeader: case CppTools::ProjectFile::CXXHeader:

View File

@@ -754,6 +754,8 @@ void QbsProject::updateDocuments(const QSet<QString> &files)
static CppTools::ProjectFile::Kind cppFileType(const qbs::ArtifactData &sourceFile) static CppTools::ProjectFile::Kind cppFileType(const qbs::ArtifactData &sourceFile)
{ {
if (CppTools::ProjectFile::isAmbiguousHeader(sourceFile.filePath()))
return CppTools::ProjectFile::AmbiguousHeader;
if (sourceFile.fileTags().contains(QLatin1String("hpp"))) if (sourceFile.fileTags().contains(QLatin1String("hpp")))
return CppTools::ProjectFile::CXXHeader; return CppTools::ProjectFile::CXXHeader;
if (sourceFile.fileTags().contains(QLatin1String("cpp"))) if (sourceFile.fileTags().contains(QLatin1String("cpp")))

View File

@@ -442,6 +442,7 @@ void QmakeProject::updateCppCodeModel()
const QString name = generatedFile.toString(); const QString name = generatedFile.toString();
const ProjectFile::Kind kind = ProjectFile::classify(name); const ProjectFile::Kind kind = ProjectFile::classify(name);
switch (kind) { switch (kind) {
case ProjectFile::AmbiguousHeader:
case ProjectFile::CHeader: case ProjectFile::CHeader:
case ProjectFile::CSource: case ProjectFile::CSource:
case ProjectFile::CXXHeader: case ProjectFile::CXXHeader:

View File

@@ -0,0 +1,216 @@
/****************************************************************************
**
** 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 "googletest.h"
#include "gtest-qt-printing.h"
#include <cpptools/cppprojectfilecategorizer.h>
#include <utils/mimetypes/mimedatabase.h>
#include <QDebug>
#include <QFileInfo>
using CppTools::ProjectFile;
using CppTools::ProjectFiles;
using CppTools::ProjectFileCategorizer;
using testing::IsNull;
using testing::NotNull;
using testing::Eq;
using testing::Gt;
using testing::Contains;
using testing::EndsWith;
using testing::AllOf;
namespace CppTools {
bool operator==(const ProjectFile &lhs, const ProjectFile &rhs)
{
return lhs.path == rhs.path
&& lhs.kind == rhs.kind;
}
void PrintTo(const ProjectFile &projectFile, std::ostream *os)
{
*os << "ProjectFile(";
QString output;
QDebug(&output) << projectFile;
*os << qPrintable(output);
*os << ")";
}
} // namespace CppTools
namespace {
class ProjectFileCategorizer : public ::testing::Test
{
protected:
void SetUp() override;
static ProjectFiles singleFile(const QString &filePath, ProjectFile::Kind kind);
static void initMimeDataBaseIfNotYetDone();
protected:
const QString dummyProjectPartName;
};
TEST_F(ProjectFileCategorizer, C)
{
const QStringList inputFilePaths = QStringList() << "foo.c" << "foo.h";
const ProjectFiles expected {
ProjectFile("foo.c", ProjectFile::CSource),
ProjectFile("foo.h", ProjectFile::CHeader),
};
::ProjectFileCategorizer categorizer{dummyProjectPartName, inputFilePaths};
ASSERT_THAT(categorizer.cSources(), Eq(expected));
ASSERT_TRUE(categorizer.cxxSources().isEmpty());
ASSERT_TRUE(categorizer.objcSources().isEmpty());
ASSERT_TRUE(categorizer.objcxxSources().isEmpty());
}
TEST_F(ProjectFileCategorizer, CxxWithUnambiguousHeaderSuffix)
{
const QStringList inputFilePaths = QStringList() << "foo.cpp" << "foo.hpp";
const ProjectFiles expected {
ProjectFile("foo.cpp", ProjectFile::CXXSource),
ProjectFile("foo.hpp", ProjectFile::CXXHeader),
};
::ProjectFileCategorizer categorizer{dummyProjectPartName, inputFilePaths};
ASSERT_THAT(categorizer.cxxSources(), Eq(expected));
ASSERT_TRUE(categorizer.cSources().isEmpty());
ASSERT_TRUE(categorizer.objcSources().isEmpty());
ASSERT_TRUE(categorizer.objcxxSources().isEmpty());
}
TEST_F(ProjectFileCategorizer, CxxWithAmbiguousHeaderSuffix)
{
const QStringList inputFilePaths = QStringList() << "foo.cpp" << "foo.h";
const ProjectFiles expected {
ProjectFile("foo.cpp", ProjectFile::CXXSource),
ProjectFile("foo.h", ProjectFile::CXXHeader),
};
::ProjectFileCategorizer categorizer{dummyProjectPartName, inputFilePaths};
ASSERT_THAT(categorizer.cxxSources(), Eq(expected));
ASSERT_TRUE(categorizer.cSources().isEmpty());
ASSERT_TRUE(categorizer.objcSources().isEmpty());
ASSERT_TRUE(categorizer.objcxxSources().isEmpty());
}
TEST_F(ProjectFileCategorizer, ObjectiveC)
{
const QStringList inputFilePaths = QStringList() << "foo.m" << "foo.h";
const ProjectFiles expected {
ProjectFile("foo.m", ProjectFile::ObjCSource),
ProjectFile("foo.h", ProjectFile::ObjCHeader),
};
::ProjectFileCategorizer categorizer{dummyProjectPartName, inputFilePaths};
ASSERT_THAT(categorizer.objcSources(), Eq(expected));
ASSERT_TRUE(categorizer.cxxSources().isEmpty());
ASSERT_TRUE(categorizer.cSources().isEmpty());
ASSERT_TRUE(categorizer.objcxxSources().isEmpty());
}
TEST_F(ProjectFileCategorizer, ObjectiveCxx)
{
const QStringList inputFilePaths = QStringList() << "foo.mm" << "foo.h";
const ProjectFiles expected {
ProjectFile("foo.mm", ProjectFile::ObjCXXSource),
ProjectFile("foo.h", ProjectFile::ObjCXXHeader),
};
::ProjectFileCategorizer categorizer{dummyProjectPartName, inputFilePaths};
ASSERT_THAT(categorizer.objcxxSources(), Eq(expected));
ASSERT_TRUE(categorizer.objcSources().isEmpty());
ASSERT_TRUE(categorizer.cSources().isEmpty());
ASSERT_TRUE(categorizer.cxxSources().isEmpty());
}
TEST_F(ProjectFileCategorizer, MixedCAndCxx)
{
const QStringList inputFilePaths = QStringList() << "foo.cpp" << "foo.h"
<< "bar.c" << "bar.h";
const ProjectFiles expectedCxxSources {
ProjectFile("foo.cpp", ProjectFile::CXXSource),
ProjectFile("foo.h", ProjectFile::CXXHeader),
ProjectFile("bar.h", ProjectFile::CXXHeader),
};
const ProjectFiles expectedCSources {
ProjectFile("bar.c", ProjectFile::CSource),
ProjectFile("foo.h", ProjectFile::CHeader),
ProjectFile("bar.h", ProjectFile::CHeader),
};
::ProjectFileCategorizer categorizer{dummyProjectPartName, inputFilePaths};
ASSERT_THAT(categorizer.cxxSources(), Eq(expectedCxxSources));
ASSERT_THAT(categorizer.cSources(), Eq(expectedCSources));
ASSERT_TRUE(categorizer.objcSources().isEmpty());
ASSERT_TRUE(categorizer.objcxxSources().isEmpty());
}
TEST_F(ProjectFileCategorizer, AmbiguousHeaderOnly)
{
::ProjectFileCategorizer categorizer{dummyProjectPartName, QStringList() << "foo.h"};
ASSERT_THAT(categorizer.cSources(), Eq(singleFile("foo.h", ProjectFile::CHeader)));
ASSERT_THAT(categorizer.cxxSources(), Eq(singleFile("foo.h", ProjectFile::CXXHeader)));
ASSERT_THAT(categorizer.objcSources(), Eq(singleFile("foo.h", ProjectFile::ObjCHeader)));
ASSERT_THAT(categorizer.objcxxSources(), Eq(singleFile("foo.h", ProjectFile::ObjCXXHeader)));
}
void ProjectFileCategorizer::SetUp()
{
initMimeDataBaseIfNotYetDone();
}
QVector<CppTools::ProjectFile>ProjectFileCategorizer::singleFile(const QString &filePath,
ProjectFile::Kind kind)
{
return { ProjectFile(filePath, kind) };
}
void ProjectFileCategorizer::initMimeDataBaseIfNotYetDone()
{
static bool isInitialized = false;
if (isInitialized)
return;
const QString filePath = TESTDATA_DIR "/../../../../src/plugins/cpptools/CppTools.mimetypes.xml";
ASSERT_TRUE(QFileInfo::exists(filePath));
Utils::MimeDatabase::addMimeTypes(filePath);
isInitialized = true;
}
} // anonymous namespace

View File

@@ -28,6 +28,7 @@ SOURCES += \
clientserverinprocess-test.cpp \ clientserverinprocess-test.cpp \
clientserveroutsideprocess.cpp \ clientserveroutsideprocess.cpp \
lineprefixer-test.cpp \ lineprefixer-test.cpp \
cppprojectfilecategorizer-test.cpp \
processevents-utilities.cpp \ processevents-utilities.cpp \
readandwritemessageblock-test.cpp \ readandwritemessageblock-test.cpp \
sizedarray-test.cpp \ sizedarray-test.cpp \