Affinity Produkte in Version 1.x stürzen beim Starten unter macOS Sonoma ab

Wer wie ich gerne die Produkte von Affinity nutzt, also

hat mitunter nach dem Upgrade auf macOS Sonoma das Problem, dass diese direkt beim Start abstürzen.

Affinity Photo Screenshot

Auf dieser Website “https://forum.affinity.serif.com/index.php?/topic/192499-known-issue-affinity-v1-apps-crash-on-launch-when-using-intelrosetta-on-macos-sonoma-with-light-ui-style/” wird das Problem auch beschrieben und gelöst. Es liegt wohl an einem Problem des eingstellten UI-Stiels. Sofern dieser auf “Light UI” eingestellt ist, stürzt die Software ab. Das könnte auch der Grund dafür sein, dass das Programm auf meinem anderen Mac funktioniert, denn dort ist das Default-UI eingestellt, weswegen ich verwundert war.

Das Problem ist laut Website einfach zu lösen, bei mir hat es direkt funktioniert:

  1. Terminal öffnen
  2. Alte Konfigurationen löschen
    • defaults delete com.seriflabs.affinityphoto (base)
    • defaults delete com.seriflabs.affinitydesigner (base)
    • defaults delete com.seriflabs.affinitypublisher
  3. Affinity Produkte starten

Mini-Kurztipp: LLVM für Windows

Ein wenig peinlich, aber vielleicht geht es dem ein oder anderem Benutzer unixoider Betriebssysteme genauso: auf FreeBSD nutze ich clang(++), auf macOS auch. Auf Linux kann ich das auch. Ich habe jetzt erst bemerkt, dass es auch auf Windows geht. Und noch mehr: die Libs, die am Ende herausfallen, sind mit denen von Visual Studio kompatibel.

LLVM (clang++) auf Windows

Wer es ausprobieren möchte, findet die Downloads hier.

Ein paar Gedanken: Ist statische Code-Analyse und Code-Coverage ein Garant für guten Code und gute Software?

In den letzten Wochen setzte ich mich mit verschiedenen Werkzeugen zur Verbesserung meines Codes und dem Code anderer auseinander. Teilweise laß und hörte ich, dass durch statische Code-Analyse sowie Code-Coverage, einfach gesagt, der eigene Code und die eigene Software besser wird. Dabei hatte ich ein flaues Gefühl im Magen.

Ich sah mir also folgende Dinge an: SonarQube für die statische Analyse von Quellcode, Google-Test mit gcov beziehungsweise llvm-cov für Code-Coverage, aber auch zusätzlich clang-format und clang-tidy, um einheitliche Codeformatierung hinzubekommen und bereits Warnings zur Bearbeitungszeit zu Problemen in meinem Code. Dies versuchte ich mittels VIM und ALE abzudecken, wobei es da zu größeren, bisher ungelösten Problemen kam.

SonarQube

SonarQube gibt es in verschiedenen Lizensierungsmodellen. Neben der Community-Edition, die nur wenige Programmier- und Auszeichnungssprachen beherrscht, gibt es beispielsweise auch die Developer-Edition, die einige mehr kann. Meine Erfahrungen habe ich mit C++-Code versucht, zu machen. Dementsprechend kommt man um die Developer-Edition nicht herum. Diese ist kostenpflichtig und wird nach zu analysierenden Zeilen von Code gezahlt. Das kann schnell teuer werden, zum jetzigen Zeitpunkt, Oktober 2023, kostet das System 150 USD für 100.000 Zeilen Quellcode pro Jahr. SonarQube unterteilt sich in unterschiedliche Programme, sofern man es für C++ (und C und Objective-C) einsetzt: Die Website, auf der die Ergebnisse illustriert werden, den build-wrapper, der aber auch durch compiledb ersetzt werden kann und den sonar-scaner. Die SonarQube-Werkzeuge sind in Java geschrieben und bringen ihr eigenes JRE mit, können aber auch mit einem anderen genutzt werden. Hier ist Vorsicht geboten, sind die Scripte, die die SonarQube-Komponenten aufrufen, in der Shebang doch fest auf /bin/bash gestellt, was auf verschiedenen Betriebssystemen zu Problemen führen kann.

Nicht jedes jedes System wird mit allen Tools unterstützt. Beispielsweise wird macOS arm64 nicht vom build-wrapper unterstützt, so dass man schlussendlich auf compiledb ausweichen muss. Das impliziert: verschiedene Architekturen, verschiedene Tools. Darum soll es hier aber nicht gehen.

Der Vorgang ist folgender: nachdem SonarQube installiert und initial konfiguriert wurde, erstellt man sein erstes eigenes Projekt und erhält einen Token, den man dann beim sonar-scanner entweder auf der Kommandozeile übergibt oder in die SonarQube-Konfiguration einträgt. Dann lässt man sein Make-System einmal mit dem build-wrapper oder compiledb durchlaufen, damit SonarQube weitere Informationen erhält. Zum Schluß wird der sonar-scanner gestartet, der dann die Code-Analyse durchführt, das Coverage verarbeitet, das aber durch andere Tools vorher erstellt werden muss, und die Ergebnisse ins Internet schiebt, so dass sie auf der Website erscheinen.

So, wie ich das verstanden habe, besteht die Analyse aus verschiedenen Teilbereichen. Es wird teils nach echten Bugs geschaut, teils nach sogennanten Code-Smells. SonarQube erkannte tatsächlich sowas:

auto *x = new y();
if (x) {
    //
} else {
    x->method();
}

Aber auch sowas:

const int x = 10;
return x == 10 ? a : b;

SonarQube stufte diese Fehler als Bugs ein und markierte sie als kritisch, was auch gut war. Womit ich nicht zufrieden war, war die Kategorisierung der anderen Regeln. Dies lässt sich in der Konfiguration zwar abändern, aber ich gebe diese zwei Beispiele trotzdem. Bei beiden geht es um sogenannte Code-Smells. Dabei handelt es sich um Code-Konventionen, die keine Bugs sind, sondern dem in SonarQube eingestellten Regelwerk unterliegen und gefixt werden sollten. Im ersten Fall gilt eine Einrückungstiefe innerhalb von Kontrollstrukturen tiefer als drei als kritisch:

if (true) {
    if (true) {
        if (true) {
            if (true) {
            }
        }
    }
}

Im Gegensatz dazu wurde folgender Code “nur” als Major eingestuft, obwohl er meiner Meinung nach kritisch sein sollte:

int var = 10;
if (true)
    int var = 5;

Man sieht klar, dass das Regelwerk für jeden Programmierer oder jedes Projekt oder jedes Team innerhalb verschiedener Konstellationen angepasst werden muss.

Aber kommen wir zur wichtigen Frage: wird mein Code und/oder meine Software durch den Einsatz von statischer Code-Analyse, wie mit SonarQube, automatisch gut?

Meine Antwort darauf ist nein. Das Positive war, dass SonarQube im Projekt zwei potentielle Bugs gefunden hat, das meiste Andere war eher kosmetischer Natur. Dabei kennt SonarQube keinen Kontext. Es prüft nicht, ob Abläufe richtig sind, ob externe Bibliotheken mit hineinspielen, ob Toolkits bestimmte Voraussetzungen haben, an die man sich halten muss und es prüft natürlich auch nicht die Logik oder Algorithmen. Weiterhin verpflichtet SonarQube nicht. Mittels // NOSONAR direkt im Code schaltet man alle Prüfungen für die Zeile ab. Je nach Situation macht das durchaus Sinn, kann aber natürlich auch vom “faulen” Programmierer missbraucht werden.

Ein anderes Problem ist, dass je nach Regelwerk der Code komplexer und auch schwieriger zu lesen wird und der Programmierer mitunter beginnt, an diesem Regelwerk vorbeizuarbeiten. Je nach Firma und Verständnis ist die Übersichtseite (z.B. im Falle von SonarQube) natürlich gut für jeden einsehbar und auch Entscheidungsträger können hier anhand von roten, gelben und grünen Symbolen auf die vermeintliche Qualität der Software schielen und Mitarbeiter dementsprechend beurteilen. Somit könnte der Programmierer natürlich versuchen, gewisse, nervende Regeln zu umgehen. Ein Beispiel: In SonarQube dürfen Klassen aus nicht mehr als 35 Methoden und 20 Membern bestehen. Spätestens bei GUIs kann das schnell zum Problem werden. Diese Klassen dann aufzusplitten, erhöht den Aufwand, die Komplexität und bietet aber mitunter keinen vernünftigen Nutzwert. In diesem Fall könnte der Programmierer beginnen – bei C++ – mit inneren Klassen und/oder Structs zu arbeiten, um dieser Problematik zu entgehen, da er sicher nicht die gesamte GUI-Datei in der Konfiguration ausklammern möchte. Dabei könnte dann folgendes herauskommen:

class MyGUI : public GUI {

public:
    struct UI {
        Button b0;
        Button b1;
    };
    UI ui;

};

Das stimmt SonarQube an der Stelle glücklich. Wie man aber sieht, wird der Code selbst umfangreicher.

Die Qualität kann zunehmen, muss sie aber nicht. Für mich ist an der Stelle statische Codeanalyse ein Werkzeug für mich, aber absolut kein Garant für guten Code, noch für gute Software.

Code-Coverage

Code-Coverage ist vom Wording meiner Meinung nach nicht ganz korrekt. Test-Code-Coverage oder ähnlich sollte es eher immer heißen.

Damit ist gemeint, dass mit Testszenarien (Unit-Tests, Intergrations-Tests, …) eine gewisse Menge Code getestet wird, also abgedeckt ist. Nimmt man hier als Beispiel gcov, ist es recht einfach. Man kompiliert sein Programm mit –coverage und führt es aus. Beim Kompilieren und Ausführen werden verschiedene Parameter gesammelt. Wichtig hier ist, welche Zeile im Code aufgerufen wird. Wird sie mindestens einmal aufgerufen, gilt sie als abgedeckt.

Hier sieht man, dass mitunter Funktionen oder Methoden selbst nicht explizit aufgerufen werden, sondern implizit und dennoch gelten sie als gecovert. Auch sagt Code-Coverage nichts darüber aus, wie sinnvoll ein Test ist, ob es nicht noch weitere Tests geben müsste, ob wirklich alles getestet ist und damit, ob die Qualität der eigenen Software verbessert wurde.

Noch ein Wort zu Formattern und Lintern

Beide Teile, also Formatter und Linter, sind spätestens im Team natürlich durchaus praktisch. Der Linter zeigt an, wo es noch Probleme bei Konventionen im Code geben könnte. Hier funktioniert clang-tidy recht gut. Der Formatter formatiert den Code so, dass er für alle gleich ist. Sprich, er achtet auf Einrückungen und ähnliches.

Fazit

Also, was denke ich? Wenn man diese Programme nutzt, als das, was sie sind, können sie dem Programmierer durchaus nützlich sein. Selbst, wenn man aus verschiedenen Gründen nicht alle Regeln einhalten kann oder möchte, regen sie doch zum Nachdenken an. Aber sie sichern selbst nicht die Qualität des Codes. Das muss der Entwickler selbst tun, wobei es sich hier um einen iterativen Prozess handeln kann.

Diese Tools dürfen aber niemals zum Einsatz gegen den Programmierer genutzt werden, sondern bei Problemen sollte dieser immer erst befragt werden.

Extra-Mouse-Buttons unter macOS mit Mac Mouse Fix konfigurieren

Ich benutze Trackballs jetzt seit 2009. Bisher hatte ich immer zu Logitech gegriffen, die Qualität von Logitech ist allerdings, meiner Meinung nach, stark verbesserungswürdig. Aus Interesse griff ich jetzt zu einer anderen Firma, die günstige Trackballs herstellt. Ich erwartete nichts, wurde aber sehr positiv überrascht. Für gut 36 Euro bietet die Firma nulea günstige Trackballs an, die vergleichbar mit Logitech Ergo 575 sind. Sie bieten allerdings mehr. Sie haben einen integrierten Akku, bieten eine DPI-Auswahl als Hardwarebutton und auch zwei Bluetooth-Kanäle.

nulea Trackball

Genauso wie der Logitech Trackball, hat der Trackball von nulea zwei Seitentasten. Die sind eigentlich für “vor” und “zurück” im Browser. Ich belege sie aber gerne mit Exposé bei macOS. Das ging so aber leider nicht, da macOS hier nichts anbietet und nulea keine Treiber bereit stellt.

Gefunden habe ich Mac Mouse Fix, ein kostenloses Programm, mit dem sich Fremdmäuse und -trackballs einfach konfigurieren lassen. Vielleicht hilft es dem ein oder anderen ja.

Mac Mouse Fix

wxWidgets auf Windows per Kommandozeile mit Visual Studio kompilieren

Wer schnell und einfach wxWidgets auf Windows auf der Kommandozeile kompilieren will, öffnet ein Visual Studio Terminal, also ein Terminal, in dem die Umgebungsvariablen für VS gesetzt sind, navigiert zu den Sourcen von wxWidgets und dort ins Verzeichnis build\msw und kann via nmake den Kompilationsprozess in Gang setzen:

nmake /f makefile.vc BUILD=release SHARED=0 TARGET_CPU=X64 RUNTIME_LIBS=static

Wir sehen am Beispiel, dass wir eine Release-Version von wxWidgets bauen, also keine Debug-Informationen drin sind (ansonsten release einfach durch debug tauschen). SHARED=0 bedeutet, dass wir keine dynamischen Libraries bauen möchten (wenn doch, einfach weglassen). Die restlichen Parameter sollten ebenso selbsterklärend sein.

Ein interessanter Fehler beim Testen von Software

In der Firma, in der ich arbeite, ist mir ein Malheur passiert, und zwar beim Testen der von mir geschriebenen Software. Ich will (vielleicht darf) ich keine Details verraten, aber es geht um ein Programm, welches ein anderes Programm startet, überwacht und beendet. Nichts Großes, nichts Kompliziertes. Eine Sache muss man aber wissen: das Programm, an dem ich arbeite und das Programm, dass von meinem gestartet wird, soll auf macOS arm64 und macOS amd64 laufen und ist keine Fat-Binary, was bedeutet, dass es für die jeweilige Plattform immer ein Bundle gibt.

Jetzt ging es zum Glück nur um eine Testversion, die ich meinen Kollegen zum Ausprobieren geben wollte. Ich packte mein Programm also, zusammen mit dem Fremdprogramm. Einmal für macOS amd64, einmal für macOS arm64 (und noch weitere Plattformen). Faul, wie ich bin, hatte ich dafür ein Script geschrieben. Leider habe ich nicht weit genug automatisiert, denn hier passierte das Problem: Ich packte mein Programm für amd64 und das Fremdprogramm für arm64. Bevor ich meine Software zum Testen herausgebe, probiere ich diese immer einmal aus, zumindest im kleinen Rahmen. Eine CI-CD-Pipeline gab es in dem frühen Entwicklungsstadium leider noch nicht (Das ist ein Fehler! Kümmert euch direkt darum!). Jetzt hätte das Problem beim Testen auffallen müssen, aber hier beging ich den entscheidenden großen Fehler in der gesamten, sehr suboptimalen Kette: Ich probierte die arm64 und die amd64 meine Software und der Fremdsoftware nur auf meiner arm64-macOS-VM aus.

Wer aufgepasst hat, kann sich sicher denken, was jetzt passiert ist: Da Rosetta 2 lief, startete sie natürlich mein amd64-Programm und mein amd64-Programm startete das arm64-Fremdprogramm, ohne, dass ich was von einem Fehler mitbekam.

Ich lieferte natürlich die amd64-Version aus und es kam, wie es kommen musste: bei den Kollegen mit amd64 macOS funktionierte zwar mein Programm, das Fremdprogramm aber natürlich nicht, da es kein Rosetta-Äquivalent auf macOS amd64 gibt.

Dabei hatte ich den Fehler die ganze Zeit auf dem Schirm, denn mein Paketierungsscript lieferte Hinweise, wenn die Architektur der Fremdsoftware mit der Architektur meines Programms nicht zusammenpasst. Aber ein Hinweis lässt sich im Stress leicht übersehen. Hier hätte ich doch besser einen Abbruch eingebaut.

Was lernen wir daraus? Am Besten vollständig automatisierte Tests implementieren, aber wenn man für verschiedene Architekturen händisch testet, das dann auch direkt auf der jeweiligen Architektur durchführen und nicht in einer Emulation.

AESNI und FreeBSD – “Crypto: accelerated software” nutzt Hardware

Nur kurz am Rande: meine Verwirrung war groß, als ich bei geli las “Crypto: accelerated software”. Ich dachte, der Hardwareteil der Algorithmik für AESNI würde nicht genutzt. Also suchte ich zuerst im BIOS meines HP MicroServer Gen8 mit XEONProzessor nach AES, wurde aber nicht fündig. Dann durchsuchte ich das Internet.

Durch bsdforen.de fanden wir aber heraus: es gab lediglich eine Namensänderung (:. Es steht jetzt “accelerated software” drin und bedeutet, dass die Hardware mitgenutzt wird, ansonsten sollte nur “software” darin stehen.

Java: JAR-Klassen-Versionsproblematik

Ja, nach langer langer Pause musste ich mich wieder einmal mit Java auseinander setzen. Dabei stieß ich auf ein Problem: Ich wollte JDBC-Treiber in einer bereits vorhandenen Version eines Programms einbinden. Soweit kein Problem, doch nicht alle Treiber funktionierten. Ich bekam keine (sinnvolle) Fehlermeldung, sondern Dinge funktionierten einfach nicht.

Ich fand dann heraus, dass mein JDK mitunter zu alt war, um mit den Klassendateien im JAR zu laufen. hier hätte ich mir eine vernünftige Fehlermeldung gewünscht, es kam aber keine.

Da ich für das Deploying (und nur für das Deploying) diese Problematik testen und Fehler werfen wollte, überlegte ich, wie ich vorzugehen habe. Zuerst dachte ich naiv, dass JAR-Dateien versioniert sein könnten. Natürlich völliger Quatsch, sind das ja einfach nur Archive, in denen alles Mögliche drin sein kann. So ist es auch: die darin enthaltenen Klassen-Dateien (.class) können für unterschiedliche JRE-Versionen kompiliert worden sein.

Allerdings kann man recht einfach herausfinden, für welche Version die Klassendateien kompiliert wurden: Im fünften und sechsten Byte der class-Datei steht die Minor-Version (also Byte-Offset 4 und 5), im siebten und achten Byte steht die Major-Version: s. Wikipedia.

Ich schrieb also ein Programm in C, welches die libzip nutzt, um an die Dateien im JAR zu kommen, las dann Byte 7 und 8 aus (Offset 6 und 7) (auf Endianess achten!) und fand somit die höchste und niedrigste Version des JDKs heraus. Eigentlich recht einfach.

Vielleicht hilft dem ein oder anderen das.

Hier noch die Versionsnummern bis 19 (kopiert von hier):

Java SE 19 = 63 (0x3F hex)
Java SE 18 = 62 (0x3E hex)
Java SE 17 = 61 (0x3D hex)
Java SE 16 = 60 (0x3C hex)
Java SE 15 = 59 (0x3B hex)
Java SE 14 = 58 (0x3A hex)
Java SE 13 = 57 (0x39 hex)
Java SE 12 = 56 (0x38 hex)
Java SE 11 = 55 (0x37 hex)
Java SE 10 = 54 (0x36 hex)
Java SE 9 = 53 (0x35 hex)
Java SE 8 = 52 (0x34 hex)
Java SE 7 = 51 (0x33 hex)
Java SE 6.0 = 50 (0x32 hex),
Java SE 5.0 = 49 (0x31 hex)
JDK 1.4 = 48 (0x30 hex)
JDK 1.3 = 47 (0x2F hex)
JDK 1.2 = 46 (0x2E hex)
JDK 1.1 = 45 (0x2D hex)