qmake/vcxproj: Fix "CONFIG += combine" extra compilers

Extra compilers with "CONFIG += combine" were broken for qmake's vcxproj
generator since forever.

Usually, extra compilers are handled by attaching the Custom Build Tool
to the input file.  This is not possible for combine extra compilers,
because they map multiple inputs to one output.  We cannot attach the
Custom Build Tool to the output either, because this would result in
circular dependency errors (output trying to create output itself).

To fix this, we create a custom build tool fake file (.cbt) for the
output and attach the Custom Build Tool there.  This is the same trick
we do for regular extra compilers that have C++ sources as
input (e.g. the one that generates moc_predefs.h).

Pick-to: 6.2 5.15
Fixes: QTBUG-94806
Change-Id: Ib808a43fead737df91b89a1ac5e180aeae37efae
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
This commit is contained in:
Joerg Bornemann 2021-10-18 15:09:46 +02:00
parent 3681369120
commit e022ff0a8e

View File

@ -40,6 +40,8 @@
#include <qregularexpression.h> #include <qregularexpression.h>
#include <stdlib.h> #include <stdlib.h>
#include <tuple>
#include <utility>
//#define DEBUG_SOLUTION_GEN //#define DEBUG_SOLUTION_GEN
@ -806,38 +808,67 @@ void VcprojGenerator::init()
} }
} }
// Add all input files for a custom compiler into a map for uniqueness, // Helper function to create a fake file foo.cbt for the project view.
// unless the compiler is configure as a combined stage, then use the first one //
// This prevents VS from complaining about a circular dependency from "foo -> foo".
//
// The .cbt file is added as "source" of the Custom Build Tool. This means, in the project
// view, this is the file the Custom Build Tool property page is attached to.
//
// This function returns a pair with
// - the fully resolved output file path
// - the file path of the .cbt file
auto addExtraCompilerSourceWithCustomBuildToolFakeFile
= [this](const QString &compilerOutput, const ProString &extraCompiler,
const QStringList &inputs) -> std::pair<QString, QString>
{
QString realOut = replaceExtraCompilerVariables(compilerOutput, inputs, {}, NoShell);
QString out = realOut + customBuildToolFilterFileSuffix;
createCustomBuildToolFakeFile(out, realOut);
out = Option::fixPathToTargetOS(out, false);
extraCompilerSources[out] += extraCompiler.toQString();
return { realOut, out };
};
// Add all input files for a custom compiler into a map for uniqueness.
//
// Use .cbt files for the following cases:
// - CONFIG += combine
// - the input has a built-in compiler (e.g. C++ source file)
for (const ProString &quc : project->values("QMAKE_EXTRA_COMPILERS")) { for (const ProString &quc : project->values("QMAKE_EXTRA_COMPILERS")) {
const ProStringList &invar = project->values(ProKey(quc + ".input")); const ProStringList &invar = project->values(ProKey(quc + ".input"));
const QString compiler_out = project->first(ProKey(quc + ".output")).toQString(); const QString compiler_out = project->first(ProKey(quc + ".output")).toQString();
for (ProStringList::ConstIterator iit = invar.constBegin(); iit != invar.constEnd(); ++iit) {
ProStringList fileList = project->values((*iit).toKey()); QStringList inputFiles;
if (!fileList.isEmpty()) { for (auto it = invar.begin(); it != invar.end(); ++it)
if (project->values(ProKey(quc + ".CONFIG")).indexOf("combine") != -1) inputFiles += project->values(it->toKey()).toQStringList();
fileList.erase(fileList.begin() + 1, fileList.end());
for (ProStringList::ConstIterator fit = fileList.constBegin(); fit != fileList.constEnd(); ++fit) { if (project->values(ProKey(quc + ".CONFIG")).contains("combine")) {
QString file = (*fit).toQString(); // Handle "CONFIG += combine" extra compilers.
QString realOut;
QString out;
std::tie(realOut, out)
= addExtraCompilerSourceWithCustomBuildToolFakeFile(compiler_out, quc, inputFiles);
if (hasBuiltinCompiler(realOut))
extraCompilerOutputs[out] = realOut;
} else {
// Handle regular 1-to-1 extra compilers.
for (const QString &file : inputFiles) {
if (verifyExtraCompiler(quc, file)) { if (verifyExtraCompiler(quc, file)) {
if (!hasBuiltinCompiler(file)) { if (!hasBuiltinCompiler(file)) {
extraCompilerSources[file] += quc.toQString(); extraCompilerSources[file] += quc.toQString();
} else { } else {
// Create a fake file foo.moc.cbt for the project view. QString out;
// This prevents VS from complaining about a circular std::tie(std::ignore, out)
// dependency from foo.moc -> foo.moc. = addExtraCompilerSourceWithCustomBuildToolFakeFile(compiler_out,
QString realOut = replaceExtraCompilerVariables( quc,
compiler_out, file, QString(), NoShell); QStringList(file));
QString out = realOut + customBuildToolFilterFileSuffix;
createCustomBuildToolFakeFile(out, realOut);
out = Option::fixPathToTargetOS(out, false);
extraCompilerSources[out] += quc.toQString();
extraCompilerOutputs[out] = file; extraCompilerOutputs[out] = file;
} }
} }
} }
} }
} }
}
#if 0 // Debugging #if 0 // Debugging
for (auto it = extraCompilerSources.cbegin(), end = extraCompilerSources.cend(); it != end; ++it) for (auto it = extraCompilerSources.cbegin(), end = extraCompilerSources.cend(); it != end; ++it)
@ -1545,9 +1576,10 @@ void VcprojGenerator::initExtraCompilerOutputs()
if (!outputs.isEmpty()) if (!outputs.isEmpty())
tmp_out = outputs.first().toQString(); tmp_out = outputs.first().toQString();
if (project->values(ProKey(*it + ".CONFIG")).indexOf("combine") != -1) { if (project->values(ProKey(*it + ".CONFIG")).indexOf("combine") != -1) {
// Combined output, only one file result // Combined output, only one file result. Use .cbt file.
extraCompile.addFile(Option::fixPathToTargetOS( extraCompile.addFile(Option::fixPathToTargetOS(
replaceExtraCompilerVariables(tmp_out, QString(), QString(), NoShell), false)); replaceExtraCompilerVariables(tmp_out + customBuildToolFilterFileSuffix,
QString(), QString(), NoShell), false));
} else if (!inputVars.isEmpty()) { } else if (!inputVars.isEmpty()) {
// One output file per input // One output file per input
const ProStringList &tmp_in = project->values(inputVars.first().toKey()); const ProStringList &tmp_in = project->values(inputVars.first().toKey());