diff --git a/pkgs/development/libraries/qt-6/modules/qtdeclarative/default.nix b/pkgs/development/libraries/qt-6/modules/qtdeclarative/default.nix index 66bda00fe566f9..8caec047ac30be 100644 --- a/pkgs/development/libraries/qt-6/modules/qtdeclarative/default.nix +++ b/pkgs/development/libraries/qt-6/modules/qtdeclarative/default.nix @@ -7,6 +7,7 @@ stdenv, lib, pkgsBuildBuild, + replaceVars, }: qtModule { @@ -21,12 +22,25 @@ qtModule { strictDeps = true; patches = [ - # prevent headaches from stale qmlcache data - ./disable-disk-cache.patch + # invalidates qml caches created from nix applications at different + # store paths and disallows saving caches of bare qml files in the store. + (replaceVars ./invalidate-caches-from-mismatched-store-paths.patch { + nixStore = builtins.storeDir; + nixStoreLength = builtins.toString ((builtins.stringLength builtins.storeDir) + 1); # trailing / + }) # add version specific QML import path ./use-versioned-import-path.patch ]; + preConfigure = let + storePrefixLen = builtins.toString ((builtins.stringLength builtins.storeDir) + 1); + in '' + # "NIX:" is reserved for saved qmlc files in patch 0001, "QTDHASH:" takes the place + # of the old tag, which is otherwise the qt version, invalidating caches from other + # qtdeclarative store paths. + echo "QTDHASH:''${out:${storePrefixLen}:32}" > .tag + ''; + cmakeFlags = [ "-DQt6ShaderToolsTools_DIR=${pkgsBuildBuild.qt6.qtshadertools}/lib/cmake/Qt6ShaderTools" @@ -37,4 +51,6 @@ qtModule { ++ lib.optionals (!stdenv.buildPlatform.canExecute stdenv.hostPlatform) [ "-DQt6QmlTools_DIR=${pkgsBuildBuild.qt6.qtdeclarative}/lib/cmake/Qt6QmlTools" ]; + + meta.maintainers = with lib.maintainers; [ milahu nickcao outfoxxed ]; } diff --git a/pkgs/development/libraries/qt-6/modules/qtdeclarative/disable-disk-cache.patch b/pkgs/development/libraries/qt-6/modules/qtdeclarative/disable-disk-cache.patch deleted file mode 100644 index 2c10263bdc841f..00000000000000 --- a/pkgs/development/libraries/qt-6/modules/qtdeclarative/disable-disk-cache.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- a/src/qml/jsruntime/qv4engine.cpp -+++ b/src/qml/jsruntime/qv4engine.cpp -@@ -2208,13 +2208,7 @@ ExecutionEngine::DiskCacheOptions ExecutionEngine::diskCacheOptions() const - { - if (forceDiskCache()) - return DiskCache::Enabled; -- if (disableDiskCache() || debugger()) -- return DiskCache::Disabled; -- static const DiskCacheOptions options = qmlGetConfigOption< -- DiskCacheOptions, transFormDiskCache>("QML_DISK_CACHE"); -- return hasPreview.loadAcquire() -- ? (options & ~DiskCacheOptions(DiskCache::Aot)) // Disable AOT if preview enabled -- : options; -+ return DiskCache::Disabled; - } - - void ExecutionEngine::callInContext(QV4::Function *function, QObject *self, diff --git a/pkgs/development/libraries/qt-6/modules/qtdeclarative/invalidate-caches-from-mismatched-store-paths.patch b/pkgs/development/libraries/qt-6/modules/qtdeclarative/invalidate-caches-from-mismatched-store-paths.patch new file mode 100644 index 00000000000000..364c3ed838e68c --- /dev/null +++ b/pkgs/development/libraries/qt-6/modules/qtdeclarative/invalidate-caches-from-mismatched-store-paths.patch @@ -0,0 +1,112 @@ +diff --git a/src/qml/common/qv4compileddata.cpp b/src/qml/common/qv4compileddata.cpp +index 9dee91f713..9dec5cae67 100644 +--- a/src/qml/common/qv4compileddata.cpp ++++ b/src/qml/common/qv4compileddata.cpp +@@ -15,6 +15,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 +@@ -26,6 +28,35 @@ __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("@nixStore@")); ++ 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(@nixStoreLength@); ++ auto pkgEndIdx = view.indexOf(QStringLiteral("/")); ++ if (pkgEndIdx != -1) view = view.sliced(0, pkgEndIdx); ++ ++ 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 +@@ -69,13 +100,29 @@ bool Unit::verifyHeader(QDateTime expectedSourceTimeStamp, QString *errorString) + } + + #if defined(QML_COMPILE_HASH) && defined(QML_COMPILE_HASH_LENGTH) && QML_COMPILE_HASH_LENGTH > 0 +- if (qstrncmp(qml_compile_hash, libraryVersionHash, QML_COMPILE_HASH_LENGTH) != 0) { ++ const bool nixUnit = qstrncmp("NIX:", this->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, this->libraryVersionHash, targetHashLength) != 0) { + *errorString = QStringLiteral("QML compile hashes don't match. Found %1 expected %2") + .arg(QString::fromLatin1( +- QByteArray(libraryVersionHash, QML_COMPILE_HASH_LENGTH) ++ QByteArray(this->libraryVersionHash, targetHashLength) + .toPercentEncoding()), + QString::fromLatin1( +- QByteArray(qml_compile_hash, QML_COMPILE_HASH_LENGTH) ++ QByteArray(targetHash, targetHashLength) + .toPercentEncoding())); + return false; + } +@@ -213,6 +260,29 @@ bool CompilationUnit::saveToDisk(const QUrl &unitUrl, QString *errorString) + 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. This will invalidate the caches ++ // when the store path changes. ++ const auto &applicationHash = nix__applicationHash(); ++ ++ memcpy(const_cast(unitData()->libraryVersionHash), ++ applicationHash.constData(), applicationHash.length()); ++ } else if (unitUrl.path().startsWith(QStringLiteral("@nixStore@"))) { ++ // 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 @nixStore@ 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 SaveableUnitPointer(unitData()).saveToDisk( + [&unitUrl, errorString](const char *data, quint32 size) { + const QString cachePath = localCacheFilePath(unitUrl);