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:
parent
3034d698eb
commit
5a0dcda171
@ -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)
|
||||
|
@ -26,3 +26,7 @@
|
||||
//! [3]
|
||||
rcc -binary myresource.qrc -o myresource.rcc
|
||||
//! [3]
|
||||
|
||||
//! [4]
|
||||
<file empty="true">Button.qml</file>
|
||||
//! [4]
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
|
@ -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})
|
||||
|
||||
|
@ -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"
|
||||
|
1
tests/auto/corelib/io/qresourceengine/world.txt
Normal file
1
tests/auto/corelib/io/qresourceengine/world.txt
Normal file
@ -0,0 +1 @@
|
||||
hello
|
Loading…
Reference in New Issue
Block a user