diff --git a/pkgs/development/libraries/qt-6/modules/qtdeclarative.nix b/pkgs/development/libraries/qt-6/modules/qtdeclarative.nix index 213dfd75fdc2b..9b82169511dc0 100644 --- a/pkgs/development/libraries/qt-6/modules/qtdeclarative.nix +++ b/pkgs/development/libraries/qt-6/modules/qtdeclarative.nix @@ -15,6 +15,9 @@ qtModule { strictDeps = true; patches = [ + # invalidates qml caches created from nix applications at different + # store paths and disallows saving caches of bare qml files in the store. + ../patches/qtdeclarative-invalidate-caches-from-mismatched-store-paths.patch # add version specific QML import path ../patches/0002-qtdeclarative-also-use-versioned-qml-paths.patch ]; diff --git a/pkgs/development/libraries/qt-6/patches/qtdeclarative-invalidate-caches-from-mismatched-store-paths.patch b/pkgs/development/libraries/qt-6/patches/qtdeclarative-invalidate-caches-from-mismatched-store-paths.patch new file mode 100644 index 0000000000000..ba24c1cc83b08 --- /dev/null +++ b/pkgs/development/libraries/qt-6/patches/qtdeclarative-invalidate-caches-from-mismatched-store-paths.patch @@ -0,0 +1,112 @@ +diff --git a/src/qml/jsruntime/qv4executablecompilationunit.cpp b/src/qml/jsruntime/qv4executablecompilationunit.cpp +index 2ab38b70d9..8634b07e41 100644 +--- a/src/qml/jsruntime/qv4executablecompilationunit.cpp ++++ b/src/qml/jsruntime/qv4executablecompilationunit.cpp +@@ -33,6 +33,8 @@ + #include + #include + ++#include ++ + static_assert(QV4::CompiledData::QmlCompileHashSpace > QML_COMPILE_HASH_LENGTH); + + #if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0 +@@ -44,6 +46,36 @@ __attribute__((section(".qml_compile_hash"))) + const char qml_compile_hash[QV4::CompiledData::QmlCompileHashSpace] = QML_COMPILE_HASH; + static_assert(sizeof(QV4::CompiledData::Unit::libraryVersionHash) > QML_COMPILE_HASH_LENGTH, + "Compile hash length exceeds reserved size in data structure. Please adjust and bump the format version"); ++ ++bool nix__isNixApplication() { ++ static const bool value = QCoreApplication::applicationFilePath().startsWith(QStringLiteral("/nix/store/")); ++ return value; ++} ++ ++static_assert(sizeof(QV4::CompiledData::Unit::libraryVersionHash) > ++ /*sha1*/ 20 + /*NIX:*/ 4, ++ "Nix compile hash length exceeds the reserved space in data " ++ "structure. Please review the patch."); ++ ++const QByteArray &nix__applicationHash() { ++ static const QByteArray value = [](){ ++ QCryptographicHash applicationHash(QCryptographicHash::Sha1); ++ applicationHash.addData(QByteArrayView(qml_compile_hash, QML_COMPILE_HASH_LENGTH)); ++ ++ // We only care about the package, not the specific file path. ++ auto view = QCoreApplication::applicationFilePath().sliced(11); // len("/nix/store/") ++ auto pkgEndIdx = view.indexOf(QStringLiteral("/")); ++ if (pkgEndIdx != -1) view = view.sliced(0, pkgEndIdx); ++ ++ QCryptographicHash::hashLength(QCryptographicHash::Sha1); ++ applicationHash.addData(view.toUtf8()); ++ ++ return QByteArray("NIX:") + applicationHash.result(); ++ }(); ++ ++ return value; ++} ++ + #else + # error "QML_COMPILE_HASH must be defined for the build of QtDeclarative to ensure version checking for cache files" + #endif +@@ -909,6 +941,28 @@ bool ExecutableCompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorSt + return false; + } + ++#if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0 ++ if (nix__isNixApplication() && unitUrl.scheme() == QStringLiteral("qrc")) { ++ // If the application is running from the nix store, we can safely save ++ // bytecode for its embedded QML files as long as we hash the ++ // application path into the version. ++ const auto &applicationHash = nix__applicationHash(); ++ ++ memcpy(const_cast(unitData()->libraryVersionHash), ++ applicationHash.constData(), applicationHash.length()); ++ } else if (unitUrl.path().startsWith(QStringLiteral("/nix/store/"))) { ++ // We don't store bytecode for bare QML files in the nix store as the ++ // paths will change every time the application updates, filling caches ++ // endlessly with junk. ++ *errorString = QStringLiteral("Refusing to save bytecode for bare /nix/store/ path."); ++ return false; ++ } else { ++ // If the QML file is loaded from a normal file path it doesn't matter ++ // if the application itself is running from a nix path, so we fall back ++ // to the default Qt behavior. ++ } ++#endif ++ + return CompiledData::SaveableUnitPointer(unitData()).saveToDisk( + [&unitUrl, errorString](const char *data, quint32 size) { + const QString cachePath = localCacheFilePath(unitUrl); +@@ -1038,13 +1092,29 @@ bool ExecutableCompilationUnit::verifyHeader( + } + + #if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0 +- if (qstrncmp(qml_compile_hash, unit->libraryVersionHash, QML_COMPILE_HASH_LENGTH) != 0) { ++ const bool nixUnit = qstrncmp("NIX:", unit->libraryVersionHash, 4) == 0; ++ ++ if (nixUnit && !nix__isNixApplication()) { ++ *errorString = QStringLiteral("QML compile hash is for a nix store application."); ++ return false; ++ } ++ ++ const char *targetHash = qml_compile_hash; ++ size_t targetHashLength = QML_COMPILE_HASH_LENGTH; ++ ++ if (nixUnit) { ++ const auto &applicationHash = nix__applicationHash(); ++ targetHash = applicationHash.constData(); ++ targetHashLength = applicationHash.length(); ++ } ++ ++ if (qstrncmp(targetHash, unit->libraryVersionHash, targetHashLength) != 0) { + *errorString = QStringLiteral("QML compile hashes don't match. Found %1 expected %2") + .arg(QString::fromLatin1( +- QByteArray(unit->libraryVersionHash, QML_COMPILE_HASH_LENGTH) ++ QByteArray(unit->libraryVersionHash, targetHashLength) + .toPercentEncoding()), + QString::fromLatin1( +- QByteArray(qml_compile_hash, QML_COMPILE_HASH_LENGTH) ++ QByteArray(targetHash, targetHashLength) + .toPercentEncoding())); + return false; + }