diff --git a/src/corelib/Qt6CoreMacros.cmake b/src/corelib/Qt6CoreMacros.cmake
index a3d53e8fc0..fdea5d6324 100644
--- a/src/corelib/Qt6CoreMacros.cmake
+++ b/src/corelib/Qt6CoreMacros.cmake
@@ -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
# ...
- string(APPEND qrcContents " ")
- string(APPEND qrcContents "${file}\n")
+ string(APPEND qrcContents " ${file}\n")
list(APPEND files "${file}")
set(scope_args)
diff --git a/src/corelib/doc/snippets/code/doc_src_resources.qdoc b/src/corelib/doc/snippets/code/doc_src_resources.qdoc
index a2e515ce03..f5a12ee86a 100644
--- a/src/corelib/doc/snippets/code/doc_src_resources.qdoc
+++ b/src/corelib/doc/snippets/code/doc_src_resources.qdoc
@@ -26,3 +26,7 @@
//! [3]
rcc -binary myresource.qrc -o myresource.rcc
//! [3]
+
+//! [4]
+Button.qml
+//! [4]
diff --git a/src/corelib/doc/src/cmake/cmake-properties.qdoc b/src/corelib/doc/src/cmake/cmake-properties.qdoc
index 89e7045bc7..42dfeeabe2 100644
--- a/src/corelib/doc/src/cmake/cmake-properties.qdoc
+++ b/src/corelib/doc/src/cmake/cmake-properties.qdoc
@@ -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
diff --git a/src/corelib/doc/src/resource-system.qdoc b/src/corelib/doc/src/resource-system.qdoc
index 8cee601c67..66d0c214b0 100644
--- a/src/corelib/doc/src/resource-system.qdoc
+++ b/src/corelib/doc/src/resource-system.qdoc
@@ -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
diff --git a/src/tools/rcc/rcc.cpp b/src/tools/rcc/rcc.cpp
index a87f15de33..3b5166a70e 100644
--- a/src/tools/rcc/rcc.cpp
+++ b/src/tools/rcc/rcc.cpp
@@ -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);
diff --git a/src/tools/rcc/rcc.h b/src/tools/rcc/rcc.h
index fe0e0989df..78d7600492 100644
--- a/src/tools/rcc/rcc.h
+++ b/src/tools/rcc/rcc.h
@@ -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;
diff --git a/tests/auto/corelib/io/qresourceengine/CMakeLists.txt b/tests/auto/corelib/io/qresourceengine/CMakeLists.txt
index 7a4f9ec4b7..e957a4a964 100644
--- a/tests/auto/corelib/io/qresourceengine/CMakeLists.txt
+++ b/tests/auto/corelib/io/qresourceengine/CMakeLists.txt
@@ -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})
diff --git a/tests/auto/corelib/io/qresourceengine/tst_qresourceengine.cpp b/tests/auto/corelib/io/qresourceengine/tst_qresourceengine.cpp
index cc6d92414d..71dc45ed95 100644
--- a/tests/auto/corelib/io/qresourceengine/tst_qresourceengine.cpp
+++ b/tests/auto/corelib/io/qresourceengine/tst_qresourceengine.cpp
@@ -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"
diff --git a/tests/auto/corelib/io/qresourceengine/world.txt b/tests/auto/corelib/io/qresourceengine/world.txt
new file mode 100644
index 0000000000..ce01362503
--- /dev/null
+++ b/tests/auto/corelib/io/qresourceengine/world.txt
@@ -0,0 +1 @@
+hello