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