CMake: Allow contents of resources to be replaced by empty files

This makes it possible to process QML files using qmlcachegen, and
retain the file nodes in the resource file system, but remove their
actual content from the binary. To do so, you need to mark your files
with the QT_DISCARD_FILE_CONTENTS source file property.

Fixes: QTBUG-87676
Fixes: QTBUG-103481
Fixes: QTBUG-102024
Fixes: QTBUG-102785
Change-Id: I93d5a2bfca1739ff1e0f74c8082eb8aa451b9815
Reviewed-by: Alexandru Croitor <alexandru.croitor@qt.io>
Reviewed-by: hjk <hjk@qt.io>
This commit is contained in:
Ulf Hermann 2022-11-30 14:45:48 +01:00
parent 3034d698eb
commit 5a0dcda171
9 changed files with 115 additions and 12 deletions

View File

@ -1884,10 +1884,15 @@ function(_qt_internal_process_resource target resourceName)
set(file "${CMAKE_CURRENT_SOURCE_DIR}/${file}")
endif()
get_property(is_empty SOURCE ${file} PROPERTY QT_DISCARD_FILE_CONTENTS)
### FIXME: escape file paths to be XML conform
# <file ...>...</file>
string(APPEND qrcContents " <file alias=\"${file_resource_path}\">")
string(APPEND qrcContents "${file}</file>\n")
string(APPEND qrcContents " <file alias=\"${file_resource_path}\"")
if(is_empty)
string(APPEND qrcContents " empty=\"true\"")
endif()
string(APPEND qrcContents ">${file}</file>\n")
list(APPEND files "${file}")
set(scope_args)

View File

@ -26,3 +26,7 @@
//! [3]
rcc -binary myresource.qrc -o myresource.rcc
//! [3]
//! [4]
<file empty="true">Button.qml</file>
//! [4]

View File

@ -421,6 +421,33 @@ the property value overrides the runtime path where the resource file is found.
\sa{The Qt Resource System}
*/
/*!
\page cmake-source-file-property-qt-discard-file-contents.html
\ingroup cmake-source-file-properties-qtcore
\title QT_DISCARD_FILE_CONTENTS
\target cmake-source-file-property-QT_DISCARD_FILE_CONTENTS
\summary {Specifies that the given files should be empty in the resource file system}
\cmakepropertysince 6.5
\preliminarycmakeproperty
When using the target-based variant of \l{qt6_add_resources}{qt_add_resources}
or \l{qt_add_qml_module}, setting this property to \c TRUE causes the file
contents to be omitted when creating the resource file system. The file name is
retained.
This is useful if you want to strip QML source code from the binary.
\note If you omit the QML source code from the binary, the QML engine has to
rely on the the compilation units created by \l{qmlcachegen} or \l{qmlsc}.
Those are tied to the specific version of Qt they were built with. If you change
the version of Qt your application uses, they can't be loaded anymore.
\sa{The Qt Resource System}
*/
/*!
\page cmake-target-property-qt-wasm-pthread-pool-size.html
\ingroup cmake-properties-qtcore

View File

@ -166,6 +166,25 @@
The file is from the application then only accessible as \c :/cut-img.png
or \c{qrc:/cut-img.png}.
\section2 Discarding the file contents
Sometimes you want to add a file node to the resource file system but
don't actually want to add the file contents. \c .qrc files allow this
by setting the \c empty attribute to \c{true}.
\snippet code/doc_src_resources.qdoc 4
The resulting file is then still accessible from the application, but
its contents are empty.
This is useful to strip QML source code from an application binary.
\note If you omit the QML source code from the binary, the QML engine has to
rely on the the compilation units created by \l{qmlcachegen} or \l{qmlsc}.
Those are tied to the specific version of Qt they were built with. If you
change the version of Qt your application uses, they can't be loaded
anymore.
\section2 Language Selectors
Some resources need to change based on the user's locale, such as

View File

@ -82,7 +82,8 @@ public:
RCCResourceLibrary::CompressionAlgorithm compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Best,
int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT,
int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT,
bool noZstd = false);
bool noZstd = false,
bool isEmpty = false);
~RCCFileInfo();
QString resourceName() const;
@ -107,12 +108,13 @@ public:
qint64 m_dataOffset;
qint64 m_childOffset;
bool m_noZstd;
bool m_isEmpty;
};
RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo,
QLocale::Language language, QLocale::Territory territory, uint flags,
RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, int compressThreshold,
bool noZstd)
bool noZstd, bool isEmpty)
{
m_name = name;
m_fileInfo = fileInfo;
@ -127,6 +129,7 @@ RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo,
m_compressLevel = compressLevel;
m_compressThreshold = compressThreshold;
m_noZstd = noZstd;
m_isEmpty = isEmpty;
}
RCCFileInfo::~RCCFileInfo()
@ -225,14 +228,18 @@ qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
//capture the offset
m_dataOffset = offset;
QByteArray data;
//find the data to be written
QFile file(m_fileInfo.absoluteFilePath());
if (!file.open(QFile::ReadOnly)) {
*errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString());
return 0;
if (!m_isEmpty) {
//find the data to be written
QFile file(m_fileInfo.absoluteFilePath());
if (!file.open(QFile::ReadOnly)) {
*errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString());
return 0;
}
data = file.readAll();
}
QByteArray data = file.readAll();
// Check if compression is useful for this file
if (data.size() != 0) {
@ -420,6 +427,7 @@ RCCResourceLibrary::Strings::Strings() :
ATTRIBUTE_LANG("lang"_L1),
ATTRIBUTE_PREFIX("prefix"_L1),
ATTRIBUTE_ALIAS("alias"_L1),
ATTRIBUTE_EMPTY("empty"_L1),
ATTRIBUTE_THRESHOLD("threshold"_L1),
ATTRIBUTE_COMPRESS("compress"_L1),
ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm"))
@ -464,6 +472,17 @@ enum RCCXmlTag {
};
Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE);
static bool parseBoolean(QStringView value, QString *errorMsg)
{
if (value.compare("true"_L1, Qt::CaseInsensitive) == 0)
return true;
if (value.compare("false"_L1, Qt::CaseInsensitive) == 0)
return false;
*errorMsg = QString::fromLatin1("Invalid value for boolean attribute: '%1'").arg(value);
return false;
}
bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice,
const QString &fname, QString currentPath, bool listMode)
{
@ -479,6 +498,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice,
QLocale::Language language = QLocale::c().language();
QLocale::Territory territory = QLocale::c().territory();
QString alias;
bool empty = false;
auto compressAlgo = m_compressionAlgo;
int compressLevel = m_compressLevel;
int compressThreshold = m_compressThreshold;
@ -538,6 +558,11 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice,
compressThreshold = m_compressThreshold;
QString errorString;
if (attributes.hasAttribute(m_strings.ATTRIBUTE_EMPTY))
empty = parseBoolean(attributes.value(m_strings.ATTRIBUTE_EMPTY), &errorString);
else
empty = false;
if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESSALGO))
compressAlgo = parseCompressionAlgorithm(attributes.value(m_strings.ATTRIBUTE_COMPRESSALGO), &errorString);
if (errorString.isEmpty() && attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) {
@ -628,7 +653,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice,
child.isDir() ? RCCFileInfo::Directory
: RCCFileInfo::NoFlags,
compressAlgo, compressLevel, compressThreshold,
m_noZstd));
m_noZstd, empty));
if (!arc)
m_failedResources.push_back(child.fileName());
}
@ -643,7 +668,7 @@ bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice,
compressAlgo,
compressLevel,
compressThreshold,
m_noZstd)
m_noZstd, empty)
);
if (!arc)
m_failedResources.push_back(absFileName);

View File

@ -96,6 +96,7 @@ private:
const QString ATTRIBUTE_LANG;
const QString ATTRIBUTE_PREFIX;
const QString ATTRIBUTE_ALIAS;
const QString ATTRIBUTE_EMPTY;
const QString ATTRIBUTE_THRESHOLD;
const QString ATTRIBUTE_COMPRESS;
const QString ATTRIBUTE_COMPRESSALGO;

View File

@ -31,6 +31,17 @@ qt_internal_add_test(tst_qresourceengine
TESTDATA ${test_data}
)
set_source_files_properties("world.txt"
PROPERTIES QT_DISCARD_FILE_CONTENTS TRUE
)
qt_internal_add_resource(tst_qresourceengine "qt_resource_empty"
PREFIX
"/empty"
FILES
"world.txt"
)
qt_add_resources(additional_sources testqrc/test.qrc)
target_sources(tst_qresourceengine PRIVATE ${additional_sources})

View File

@ -41,6 +41,7 @@ private slots:
void setLocale();
void lastModified();
void resourcesInStaticPlugins();
void qtResourceEmpty();
private:
const QString m_runtimeResourceRcc;
@ -168,6 +169,7 @@ void tst_QResourceEngine::checkStructure_data()
#ifdef Q_OS_ANDROID
<< QLatin1String("android_testdata")
#endif
<< QLatin1String("empty")
<< QLatin1String("otherdir")
<< QLatin1String("runtime_resource")
<< QLatin1String("searchpath1")
@ -620,6 +622,14 @@ void tst_QResourceEngine::resourcesInStaticPlugins()
QVERIFY(QFile::exists(":/staticplugin/main.cpp"));
}
void tst_QResourceEngine::qtResourceEmpty()
{
QFile f(":/empty/world.txt");
QVERIFY(f.exists());
QVERIFY(f.open(QIODevice::ReadOnly));
QVERIFY(f.readAll().isEmpty());
}
QTEST_MAIN(tst_QResourceEngine)
#include "tst_qresourceengine.moc"

View File

@ -0,0 +1 @@
hello