Determine dependencies of Windows resource files

Windows resource files support a subset of C preprocessor directives.
Among others they can have #include directives.

Use QMake's own scanner to retrieve the files that are included by a
Windows resource file and add them to its dependencies.

For the test case the TestCompiler class had to be extended:
runCommand is now public, and commandOutput is less peculiar.

Fixes: QTBUG-3859
Change-Id: I138703352c37c98297c0574a9a440510c1c494b8
Reviewed-by: Oliver Wolff <oliver.wolff@qt.io>
Reviewed-by: Edward Welbourne <edward.welbourne@qt.io>
This commit is contained in:
Joerg Bornemann 2019-08-02 13:18:46 +02:00
parent 00076a2695
commit 42d32e468a
10 changed files with 93 additions and 13 deletions

View File

@ -342,7 +342,11 @@ void MingwMakefileGenerator::writeRcFilePart(QTextStream &t)
if (defines.isEmpty())
defines = ProString(" $(DEFINES)");
t << escapeDependencyPath(var("RES_FILE")) << ": " << escapeDependencyPath(rc_file) << "\n\t"
addSourceFile(rc_file, QMakeSourceFileInfo::SEEK_DEPS);
const QStringList rcDeps = QStringList(rc_file) << dependencies(rc_file);
t << escapeDependencyPath(var("RES_FILE")) << ": "
<< escapeDependencyPaths(rcDeps).join(' ') << "\n\t"
<< var("QMAKE_RC") << " -i " << escapeFilePath(rc_file) << " -o " << fileVar("RES_FILE")
<< incPathStr << defines << "\n\n";
}

View File

@ -667,6 +667,9 @@ void Win32MakefileGenerator::writeRcFilePart(QTextStream &t)
incPathStr += escapeFilePath(path);
}
addSourceFile(rc_file, QMakeSourceFileInfo::SEEK_DEPS);
const QStringList rcDeps = QStringList(rc_file) << dependencies(rc_file);
// The resource tool may use defines. This might be the same defines passed in as the
// compiler, since you may use these defines in the .rc file itself.
// As the escape syntax for the command line defines for RC is different from that for CL,
@ -678,7 +681,8 @@ void Win32MakefileGenerator::writeRcFilePart(QTextStream &t)
// Also, we need to add the _DEBUG define manually since the compiler defines this symbol
// by itself, and we use it in the automatically created rc file when VERSION is defined
// in the .pro file.
t << escapeDependencyPath(res_file) << ": " << escapeDependencyPath(rc_file) << "\n\t"
t << escapeDependencyPath(res_file) << ": "
<< escapeDependencyPaths(rcDeps).join(' ') << "\n\t"
<< var("QMAKE_RC") << (project->isActiveConfig("debug") ? " -D_DEBUG" : "")
<< defines << incPathStr << " -fo " << escapeFilePath(res_file)
<< ' ' << escapeFilePath(rc_file);

View File

@ -164,15 +164,17 @@ bool TestCompiler::runCommand(const QString &cmd, const QStringList &args, bool
return errorOut();
}
child.setReadChannel(QProcess::StandardError);
child.waitForFinished(-1);
bool ok = child.exitStatus() == QProcess::NormalExit && (expectFail ^ (child.exitCode() == 0));
foreach (const QByteArray &output, child.readAllStandardError().split('\n')) {
testOutput_.append(QString::fromLocal8Bit(output));
if (output.startsWith("Project MESSAGE: FAILED"))
ok = false;
for (auto channel : { QProcess::StandardOutput, QProcess::StandardError }) {
child.setReadChannel(channel);
while (!child.atEnd()) {
const QString output = QString::fromLocal8Bit(child.readLine());
if (output.startsWith("Project MESSAGE: FAILED"))
ok = false;
testOutput_.append(output);
}
}
return ok ? true : errorOut();

View File

@ -71,13 +71,17 @@ public:
bool removeProject( const QString &workPath, const QString &project );
// removes the file specified by 'fileName' on the 'workPath'
bool removeFile( const QString &workPath, const QString &fileName );
// returns each line of stdout of the last command append with a "new line" character(s) to suit the platform
// Returns each line of stdout/stderr of the last commands
// separated by platform-specific line endings.
QString commandOutput() const;
// clear the results of storage of stdout for running previous commands
void clearCommandOutput();
private:
bool runCommand(const QString &cmd, const QStringList &args, bool expectFail = false);
private:
bool errorOut();
QString makeCmd_;

View File

@ -0,0 +1 @@
#include "version.inc"

View File

@ -0,0 +1 @@
int main() {}

View File

@ -0,0 +1,28 @@
VS_VERSION_INFO VERSIONINFO
FILEVERSION 1,0,0,0
PRODUCTVERSION 1,0,0,0
FILEFLAGS 0x0L
FILEFLAGSMASK 0x3fL
FILEOS 0x00040004L
FILETYPE 0x1L
FILESUBTYPE 0x0L
BEGIN
BLOCK "StringFileInfo"
BEGIN
BLOCK "000004b0"
BEGIN
VALUE "CompanyName", "The Qt Company Ltd"
VALUE "FileDescription", "A Good File"
VALUE "FileVersion", "1.0.0.0"
VALUE "LegalCopyright", "Copyright (C) 2019 The Qt Company Ltd."
VALUE "InternalName", "foo"
VALUE "OriginalFilename", "foo.exe"
VALUE "ProductName", "A Good Product"
VALUE "ProductVersion", "1.0.0.0"
END
END
BLOCK "VarFileInfo"
BEGIN
VALUE "Translation", 0x0, 1200
END
END

View File

@ -0,0 +1,4 @@
QT = core
SOURCES = main.cpp
RC_FILE = windows_resources.rc
TEMPLATE = app

View File

@ -0,0 +1,2 @@
#include "winver.h"
#include "inter.inc"

View File

@ -76,8 +76,10 @@ private slots:
void findMocs();
void findDeps();
void rawString();
#if defined(Q_OS_MAC)
#if defined(Q_OS_DARWIN)
void bundle_spaces();
#elif defined(Q_OS_WIN)
void windowsResources();
#endif
void substitutes();
void project();
@ -512,7 +514,8 @@ struct TempFile
}
};
#if defined(Q_OS_MAC)
#if defined(Q_OS_DARWIN)
void tst_qmake::bundle_spaces()
{
QString workDir = base_path + "/testdata/bundle-spaces";
@ -543,7 +546,34 @@ void tst_qmake::bundle_spaces()
QVERIFY( !non_existing_file.exists() );
QVERIFY( test_compiler.removeMakefile(workDir) );
}
#endif // defined(Q_OS_MAC)
#elif defined(Q_OS_WIN) // defined(Q_OS_DARWIN)
void tst_qmake::windowsResources()
{
QString workDir = base_path + "/testdata/windows_resources";
QVERIFY(test_compiler.qmake(workDir, "windows_resources"));
QVERIFY(test_compiler.make(workDir));
// Another "make" must not rebuild the .res file
test_compiler.clearCommandOutput();
QVERIFY(test_compiler.make(workDir));
QVERIFY(!test_compiler.commandOutput().contains("windows_resources.rc"));
test_compiler.clearCommandOutput();
// Wait a second to make sure we get a new timestamp in the touch below
QTest::qWait(1000);
// Touch the deepest include of the .rc file
QVERIFY(test_compiler.runCommand("cmd", QStringList{"/c",
"echo.>>" + QDir::toNativeSeparators(workDir + "/version.inc")}));
// The next "make" must rebuild the .res file
QVERIFY(test_compiler.make(workDir));
QVERIFY(test_compiler.commandOutput().contains("windows_resources.rc"));
}
#endif // defined(Q_OS_WIN)
void tst_qmake::substitutes()
{