This is my attempt to provide the steps and blueprint patches to build a fully functional local version of KMyMoney 5.2 on Apple Silicon.
- Setup Craft Environment
Initialize the build system and source the required environment.
bash
mkdir ~/CraftRoot
curl https://raw.githubusercontent.com/KDE/craft/master/setup/CraftBootstrap.py -o setup.py
python3 setup.py --prefix ~/CraftRoot # Select macOS/arm64
# Apply the blueprint changes below before proceeding
source ~/CraftRoot/craft/craftenv.sh
- Update Blueprints with macOS Fixes
Apply the following diffs to your local blueprints to automate the build on M-series hardware.
Gwenhywfar Blueprint
File: ~/CraftRoot/etc/blueprints/locations/craft-blueprints-kde/libs/_autotools/gwenhywfar/gwenhywfar.py
Fixes Qt6 tool detection (uic/moc) and dependencies.
diff
--- gwenhywfar.py-orig
+++ gwenhywfar.py
@@ -49,6 +48,7 @@
self.runtimeDependencies["libs/gnutls"] = None
self.runtimeDependencies["libs/gcrypt"] = None
self.runtimeDependencies["libs/qt6/qtbase"] = None
+ self.runtimeDependencies["libs/qt6/qttools"] = None
@@ -98,7 +98,6 @@
if CraftCore.compiler.isMacOS:
self.subinfo.options.configure.ldflags += " -Wl,-headerpad_max_install_names"
- with utils.ScopedEnv({"PATH": f"{CraftCore.standardDirs.craftRoot() / 'libexec'}:{os.environ.get('PATH')}"}):
- return super().configure()
+ os.environ["PATH"] = f"{CraftCore.standardDirs.craftRoot() / 'libexec'}:{os.environ['PATH']}"
AqBanking Blueprint
File: ~/CraftRoot/etc/blueprints/locations/craft-blueprints-kde/libs/_autotools/aqbanking/aqbanking.py
Ensures Qt6 tools are available and fixes CMake naming conventions for macOS.
diff
--- aqbanking.py-orig
+++ aqbanking.py
@@ -49,29 +48,21 @@
self.runtimeDependencies["virtual/base"] = None
self.buildDependencies["libs/libbzip2"] = None
self.runtimeDependencies["libs/gwenhywfar"] = None
+ self.runtimeDependencies["libs/qt6/qttools"] = None
KMyMoney Blueprint
File: ~/CraftRoot/etc/blueprints/locations/craft-blueprints-kde/extragear/kmymoney/kmymoney.py
Automates the bundle merge, rescues plugins from the image folder, and injects relative metadata to fix the “Missing AqBanking Menu” and “Missing Storage Plugin” errors.
diff
--- kmymoney.py-orig
+++ kmymoney.py
@@ -36,6 +36,7 @@
def setDependencies(self):
+ self.runtimeDependencies["libs/qt6/qttools"] = None
...
@@ -73,14 +71,107 @@
if CraftCore.compiler.isMacOS:
- self.subinfo.options.configure.args += ["-DENABLE_WOOB=OFF"]
+ self.subinfo.options.configure.args += [
+ "-DENABLE_WOOB=OFF",
+ "-DCMAKE_OSX_ARCHITECTURES=arm64",
+ "-DKDE_INSTALL_BUNDLEDIR=Applications/KDE",
+ "-DAPPLE_INSTALL_BUNDLE=ON"
+ ]
def install(self):
if not super().install():
return False
- # For AppImages create a gpgconf.ctl file that forces gpgconf to use the actual APPDIR
+ if CraftCore.compiler.isMacOS:
+ imageDir = self.imageDir()
+ craftRoot = CraftCore.standardDirs.craftRoot()
+
+ # 1. FIX MISSING PLUGINS & LIBS IN CRAFTROOT
+ for folder in ["plugins/kmymoney_plugins", "lib"]:
+ if (imageDir / folder).exists():
+ utils.copyDir(imageDir / folder, craftRoot / folder)
+
+ # 2. MANUALLY MERGE THE BUNDLE
+ destBundle = craftRoot / "Applications/KDE/kmymoney.app"
+ appContents = destBundle / "Contents"
+ if not destBundle.exists():
+ utils.createDir(destBundle.parent)
+ utils.copyDir(imageDir / "Applications/KDE/kmymoney.app", destBundle)
+
+ # 3. INTERNAL BUNDLE STRUCTURE
+ utils.createDir(appContents / "Resources/lib")
+ utils.copyDir(craftRoot / "lib/gwenhywfar", appContents / "Resources/lib/gwenhywfar")
+ utils.copyDir(craftRoot / "lib/aqbanking", appContents / "Resources/lib/aqbanking")
+ utils.copyDir(craftRoot / "share/gwenhywfar", appContents / "Resources/gwenhywfar")
+ utils.copyDir(craftRoot / "share/aqbanking", appContents / "Resources/aqbanking")
+
+ # 4. BRIDGE SYMLINKS
+ for folder in ["lib", "share"]:
+ link = appContents / folder
+ if link.exists() or link.is_symlink(): utils.deleteFile(link)
+ utils.createSymlink(craftRoot / folder, link, targetIsDirectory=True)
+
+ # 5. INJECT METADATA (LSEnvironment)
+ infoPlist = appContents / "Info.plist"
+ gwenPath = f"{craftRoot}/lib/gwenhywfar/plugins/79"
+ kmmPluginPath = f"{craftRoot}/plugins"
+ utils.system(["plutil", "-replace", "LSEnvironment", "-xml",
+ f"<dict><key>GWEN_PLUGIN_DIR</key><string>{gwenPath}</string>"
+ f"<key>QT_PLUGIN_PATH</key><string>{kmmPluginPath}</string></dict>",
+ str(infoPlist)])
+
+ # 6. SAFETY CHECK: Revert wrapper if it existed
+ binDir = appContents / "MacOS"
+ if (binDir / "kmymoney.real").exists():
+ utils.deleteFile(binDir / "kmymoney")
+ utils.moveFile(binDir / "kmymoney.real", binDir / "kmymoney")
+
+ return True
def createPackage(self):
- self.defines["executable"] = "bin\\kmymoney.exe"
+ self.defines["executable"] = "bin\\kmymoney.exe"
self.defines["icon"] = self.blueprintDir() / "kmymoney.ico"
- self.defines["mimetypes"] = [...]
+ self.defines["mimetypes"] = ["application/x-kmymoney", "application/x-ofx", "application/vnd.intu.qfx", "application/x-qfx", "application/x-qif", "text/csv"]
+ self.defines["file_types"] = [".kmy", ".ofx", ".qfx", ".qif", ".csv"]
- self.defines["website"] = "https://kmymoney.org/"
+ self.defines["website"] = "https://kmymoney.org"
+
+ if CraftCore.compiler.isMacOS:
+ self.defines["apppath"] = "Applications/KDE/kmymoney.app"
+ self.defines["extra_binaries"] = [
+ "Resources/lib/gwenhywfar/plugins/79/configmgr/dir.so",
+ "Resources/lib/aqbanking/plugins/44/providers/aqhbci.so"
+ ]
+
self.blacklist_file.append(self.blueprintDir() / "blacklist.txt")
if CraftCore.compiler.isMacOS:
self.blacklist_file.append(self.blueprintDir() / "blacklist_mac.txt")
@@ -121,3 +180,4 @@
self.ignoredPackages.append("binary/mysql")
return super().createPackage()
+
- Build and Launch
Execute the build sequence from scratch.
bash
craft --set branch=master kmymoney
craft kmymoney
open "$HOME/CraftRoot/Applications/KDE/kmymoney.app"
# Package the DMG
craft --package kmymoney
This (hopefully) will provide the needed information to get the build and packaging working again on your pipeline. However, the DMG still doesn’t have the Configure AqBanking… menu (the build does, the dmg does not) which was the purpose of my previous post.