CMakeProjectManager: Fix issues with precompiled headers

Clang code model can break if CMake project uses precompiled headers.

QtCreator will make a copy of the precompiled header, this way it
will not conflict with the build system one.

Ammends 888ea6bbbb

Fixes: QTCREATORBUG-24945
Fixes: QTCREATORBUG-25213
Change-Id: I149fc416cd047683d095758a024de47c7baf681c
Reviewed-by: Christian Kandeler <christian.kandeler@qt.io>
Reviewed-by: Eike Ziller <eike.ziller@qt.io>
This commit is contained in:
Cristian Adam
2021-01-11 15:57:16 +01:00
parent a64defa10b
commit 98b92ed03e
5 changed files with 81 additions and 47 deletions

View File

@@ -219,6 +219,29 @@ bool FileUtils::copyRecursively(const FilePath &srcFilePath, const FilePath &tgt
});
}
/*!
Copies a file specified by \a srcFilePath to \a tgtFilePath only if \a srcFilePath is different
(file size and last modification time).
Returns whether the operation succeeded.
*/
bool FileUtils::copyIfDifferent(const FilePath &srcFilePath, const FilePath &tgtFilePath)
{
if (QFile::exists(tgtFilePath.toString())) {
const QFileInfo srcFileInfo = srcFilePath.toFileInfo();
const QFileInfo tgtFileInfo = tgtFilePath.toFileInfo();
if (srcFileInfo.lastModified() == tgtFileInfo.lastModified() &&
srcFileInfo.size() == tgtFileInfo.size()) {
return true;
} else {
QFile::remove(tgtFilePath.toString());
}
}
return QFile::copy(srcFilePath.toString(), tgtFilePath.toString());
}
/*!
If this is a directory, the function will recursively check all files and return
true if one of them is newer than \a timeStamp. If this is a single file, true will

View File

@@ -189,6 +189,8 @@ public:
const FilePath &tgtFilePath,
QString *error,
T &&copyHelper);
static bool copyIfDifferent(const FilePath &srcFilePath,
const FilePath &tgtFilePath);
static FilePath resolveSymlinks(const FilePath &path);
static QString fileSystemFriendlyName(const QString &name);
static int indexOfQmakeUnfriendly(const QString &name, int startpos = 0);

View File

@@ -348,10 +348,15 @@ RawProjectParts generateRawProjectParts(const PreprocessedData &input,
}
QString ending;
if (ci.language == "C")
QString qtcPchFile;
if (ci.language == "C") {
ending = "/cmake_pch.h";
else if (ci.language == "CXX")
qtcPchFile = "qtc_cmake_pch.h";
}
else if (ci.language == "CXX") {
ending = "/cmake_pch.hxx";
qtcPchFile = "qtc_cmake_pch.hxx";
}
++counter;
RawProjectPart rpp;
@@ -362,13 +367,7 @@ RawProjectParts generateRawProjectParts(const PreprocessedData &input,
rpp.setMacros(transform<QVector>(ci.defines, &DefineInfo::define));
rpp.setHeaderPaths(transform<QVector>(ci.includes, &IncludeInfo::path));
RawProjectPartFlags cProjectFlags;
cProjectFlags.commandLineFlags = splitFragments(ci.fragments);
rpp.setFlagsForC(cProjectFlags);
RawProjectPartFlags cxxProjectFlags;
cxxProjectFlags.commandLineFlags = cProjectFlags.commandLineFlags;
rpp.setFlagsForCxx(cxxProjectFlags);
QStringList fragments = splitFragments(ci.fragments);
FilePath precompiled_header
= FilePath::fromString(findOrDefault(t.sources, [&ending](const SourceInfo &si) {
@@ -383,9 +382,38 @@ RawProjectParts generateRawProjectParts(const PreprocessedData &input,
const FilePath parentDir = FilePath::fromString(sourceDir.absolutePath());
precompiled_header = parentDir.pathAppended(precompiled_header.toString());
}
rpp.setPreCompiledHeaders({precompiled_header.toString()});
// Remove the CMake PCH usage command line options in order to avoid the case
// when the build system would produce a .pch/.gch file that would be treated
// by the Clang code model as its own and fail.
auto remove = [&](const QStringList &args) {
auto foundPos = std::search(fragments.begin(), fragments.end(),
args.begin(), args.end());
if (foundPos != fragments.end())
fragments.erase(foundPos, std::next(foundPos, args.size()));
};
remove({"-Xclang", "-include-pch", "-Xclang", precompiled_header.toString() + ".gch"});
remove({"-Xclang", "-include-pch", "-Xclang", precompiled_header.toString() + ".pch"});
remove({"-Xclang", "-include", "-Xclang", precompiled_header.toString()});
remove({"-include", precompiled_header.toString()});
remove({"/FI", precompiled_header.toString()});
// Make a copy of the CMake PCH header and use it instead
FilePath qtc_precompiled_header = precompiled_header.parentDir().pathAppended(qtcPchFile);
FileUtils::copyIfDifferent(precompiled_header, qtc_precompiled_header);
rpp.setPreCompiledHeaders({qtc_precompiled_header.toString()});
}
RawProjectPartFlags cProjectFlags;
cProjectFlags.commandLineFlags = fragments;
rpp.setFlagsForC(cProjectFlags);
RawProjectPartFlags cxxProjectFlags;
cxxProjectFlags.commandLineFlags = cProjectFlags.commandLineFlags;
rpp.setFlagsForCxx(cxxProjectFlags);
const bool isExecutable = t.type == "EXECUTABLE";
rpp.setBuildTargetType(isExecutable ? ProjectExplorer::BuildTargetType::Executable
: ProjectExplorer::BuildTargetType::Library);

View File

@@ -175,14 +175,6 @@ void CompilerOptionsBuilder::addSyntaxOnly()
isClStyle() ? add("/Zs") : add("-fsyntax-only");
}
void CompilerOptionsBuilder::remove(const QStringList &args)
{
auto foundPos = std::search(m_options.begin(), m_options.end(),
args.begin(), args.end());
if (foundPos != m_options.end())
m_options.erase(foundPos, std::next(foundPos, args.size()));
}
QStringList createLanguageOptionGcc(ProjectFile::Kind fileKind, bool objcExt)
{
QStringList options;
@@ -369,42 +361,32 @@ void CompilerOptionsBuilder::addHeaderPathOptions()
}
}
void CompilerOptionsBuilder::addIncludeFile(const QString &file)
{
if (QFile::exists(file)) {
add({isClStyle() ? QLatin1String(includeFileOptionCl)
: QLatin1String(includeFileOptionGcc),
QDir::toNativeSeparators(file)});
}
}
void CompilerOptionsBuilder::addIncludedFiles(const QStringList &files)
{
QStringList result;
const QString includeOptionString
= QLatin1String(isClStyle() ? includeFileOptionCl : includeFileOptionGcc);
for (const QString &file : files) {
if (QFile::exists(file)) {
result += includeOptionString;
result += QDir::toNativeSeparators(file);
}
if (m_projectPart.precompiledHeaders.contains(file))
continue;
addIncludeFile(file);
}
m_options.append(result);
}
void CompilerOptionsBuilder::addPrecompiledHeaderOptions(UsePrecompiledHeaders usePrecompiledHeaders)
{
for (const QString &pchFile : m_projectPart.precompiledHeaders) {
// Bail if build system precompiled header artifacts exists.
// Clang cannot handle foreign PCH files.
if (QFile::exists(pchFile + ".gch") || QFile::exists(pchFile + ".pch"))
usePrecompiledHeaders = UsePrecompiledHeaders::No;
if (usePrecompiledHeaders == UsePrecompiledHeaders::No)
return;
if (usePrecompiledHeaders == UsePrecompiledHeaders::No) {
// CMake PCH will already have force included the header file in
// command line options, remove it if exists.
// In case of Clang compilers, also remove the pch-inclusion arguments.
remove({"-Xclang", "-include-pch", "-Xclang", pchFile + ".gch"});
remove({"-Xclang", "-include-pch", "-Xclang", pchFile + ".pch"});
remove({isClStyle() ? QLatin1String(includeFileOptionCl)
: QLatin1String(includeFileOptionGcc), pchFile});
} else if (QFile::exists(pchFile)) {
add({isClStyle() ? QLatin1String(includeFileOptionCl)
: QLatin1String(includeFileOptionGcc),
QDir::toNativeSeparators(pchFile)});
}
for (const QString &pchFile : m_projectPart.precompiledHeaders) {
addIncludeFile(pchFile);
}
}

View File

@@ -88,8 +88,6 @@ public:
void add(const QStringList &args, bool gccOnlyOptions = false);
virtual void addExtraOptions() {}
void remove(const QStringList &args);
static UseToolchainMacros useToolChainMacros();
void reset();
@@ -103,6 +101,7 @@ private:
QStringList wrappedQtHeadersIncludePath() const;
QStringList wrappedMingwHeadersIncludePath() const;
QByteArray msvcVersion() const;
void addIncludeFile(const QString &file);
private:
const ProjectPart &m_projectPart;