qt6.qtdeclarative: re-enable qml caching

This commit is contained in:
outfoxxed
2024-12-31 01:28:56 -08:00
parent bba38aef22
commit 78e340633e
3 changed files with 130 additions and 19 deletions
@@ -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; [ nickcao outfoxxed ];
}
@@ -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,
@@ -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 <QtCore/qscopeguard.h>
#include <QtCore/qstandardpaths.h>
+#include <QtCore/qcoreapplication.h>
+
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<char *>(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<char>(
[&unitUrl, errorString](const char *data, quint32 size) {
const QString cachePath = localCacheFilePath(unitUrl);