Meiner Depressison begegne ich damit, für Ablenkung zu sorgen und wie ginge das nachhaltiger als etwas Neues zu erlernen oder etwas vergessen Geglaubtes wieder zu erlernen. Oft sind das kleinere Projekte, die nach ein paar Tagen oder auch wenigen Stunden erledigt sind. Manchmal lässt sich das Interesse nicht konservieren oder es kommen Kosten auf mich zu, die ich nicht eingeplant hatte und dann ist es schon wieder vorbei bevor es richtig begonnen hat. Aber manchmal ergibt sich aus einem kleinen Gedanken auch ein Projekt, dass Wochen lang fesselt und von so einem möchte ich hier berichten. Das wird keine korrekte Zeitleiste ergeben, sondern Verbindungen von Entscheidungen und Begebenheiten.

Ende der 80.er Jahre habe ich in einer Umschulung eine Ausbildung zum Datenverarbeitungskaufmann gemacht und im Zuge dieser Ausbildung Cobol und C gelernt. Der Neugier kann man heute schnell Befriedigung verschaffen indem man sich die GNU Compiler Collection (GCC) mit einer passenden Toolchain installiert, die es für beide Sprachen gibt. Zwar kann man auch in Cobol schnell ein "Hello World" Programm bewerkstelligen, der mögliche Nutzen für die private Anwendung ist bei einer Sprache die explizit für Geschäftslogik entwickelt wurde aber eher marginal und so habe ich mich auf C konzentriert. Nach ersten Schritten mit Beispielprojekten und Tutorials musste ich nur eine Aufgabe finden, die es lohnenswert erscheinen ließ auch dabei zu bleiben wenn es zu stottern beginnen sollte. Ich habe erst vor ein paar Wochen von meinem welcome.sh Skript berichtet, welches ich schon sehr lange nutze und für jede Plattform anpassen muss. Ursprünglich war der Anlass das Bash-Skript zu schreiben die nervigen MOTD (Message of the Day) Ausgaben wenn ich mich an einem Server oder einer virtuellen Maschine (VM) über SSH angemeldet habe. Statt mir immer wieder zu sagen wie das Kernel genau heißt und wo ich Hilfe zur Distribution nachschlagen kann, würde ich doch gerne sehen ob das System Probleme hat oder wann ich mich zuletzt angemeldet hatte um Updates einzuspielen.

Ersetze MOTD mit einem C Binary

Und so habe ich begonnen ein paar Zeilen C in Notepad++ zu tippen und in der WSL2 unter Windows 11 in einem Ubuntu 24.04 an den GCC zu verfüttern. Da Windows eine Brücke zwischen den Systemen spannt, ist es recht einfach in einem Windows Programm wie Notepad++ zu codieren und unter Ubuntu zu compilieren. Öffne im Explorer einfach die Freigabe \\WSL$\ und klicke dich über den Namen der WSL Distribution (Ubuntu-24.04) zu deinem Home Verzeichnis durch, erstelle ein neues Verzeichnis für dein Projekt und eine neue Datei für den Sourcecode (main.c). Die kannst du jetzt problemlos mit jedem Windows Editor bearbeiten. In einem Terminal in der die Ubuntu WSL läuft lässt sich das compilieren und ausführen:

gcc main.c -o hello
./hello

Integrierte Entwicklungsumgebung

Früher oder später beginnt der ständige Wechsel zwischen den Fenstern zwar zu nerven, der Wunsch nach einem integrierten Werkzeug wird aber unumgänglich, wenn sich Fehler ohne Debugging kaum mehr eingrenzen lassen. Ich habe für diverse kleinere Projekte bereits Visual Studio Code (VS Code) installiert und so genügt ein code . in der WSL im aktuellen Projektverzeichnis aus um es in der IDE zu öffnen. Installiert habe ich dort die passenden Erweiterungen von Microsoft (C/C++, C/C++ DevTools, C/C++ Extensions Pack und CMake Tools). Jetzt reicht ein Druck auf die F5 Taste um eine Debug Version des Binaries zu erstellen und das Debugging zu starten. Es lassen sich Breakpoints setzen und Variablenwerte zur Laufzeit prüfen, eine große Erleichterung. Für einen produktiven Test habe ich den gcc Compiler zunächst weiterhin von Hand gestartet. Oft war das ja nicht notwendig, da VS Code ein Terminal in die WSL nutzt und so auch das Debugging in der selben Umgebung stattfindet.

Make Build-Prozess

Irgendwann hatte ich die fixe Idee den Sourcecode übersichtlicher zu gestallten und wollte Teile nach Themen gruppiert in eigene Dateien auslagern. Ich erstellte eine Datei für Codesegmente die ich aus Beispielen und Tutorials kopierte und vielleicht verwenden wollte, eine für das Sammeln von Systeminformationen und eine für die Ausgabe. Die letzten beiden wurden einfach per Include Anweisung in main.c eingebunden. Allerdings wurde das Kommando zum Compilieren jetzt zusehends länger und durch das Probieren weiterer Parameter machte ich Fehler bei erneuten Aufrufen die schwer zu erkennen waren. Es war an der Zeit sich Gedanken über einen wiederholsicheren Build-Prozess zu machen und so erstellte ich ein Makefile im Projekt. Ich kann nicht sagen ob ich dann Hilfe wegen der selbst erzeugten Probleme beim Compilieren oder der Erstellung des Makefile brauchte, aber zu dem Zeitpunkt befragte ich das erste mal für dieses Projekt ein LLM.

Versionsverwaltung

Den Sourcecode nach Themen auf mehrere Dateien aufzuteilen sorgt zwar für mehr Übersicht innerhalb der Anwendung, wer aber einmal nach einer Reihe von Änderungen an verschiedenen Stellen festgestellt hat das ein Projekt sich nicht mehr compilieren lässt, weiß es zu schätzen solch eine Änderung mit nur einem Kommando rückgängig machen, bzw. auf einen früheren Stand wechseln zu können. Git weiß Rat - und da ich erst einige Tage zuvor alle meine Repositories von Github zu Codeberg migriert hatte, wurde das Hello Programm zu meinem ersten nur auf Codeberg vorhandenem Repository: Linux-Hello. Bisher habe ich die Git Kommandozeile ignoriert und so muss ich gestehen, dass es mir nicht gelang das Repository von dort zu erstellen. Ich hatte mitten in der Programmierung auch keine Motivation das auszubaldowern und verweigerte mich der Idee den gerade angeheuerten Assistenten zu befragen. So habe ich dann das Repository in der Web-GUI von Codeberg erstellt und dann neben das vorhandene Projektverzeichnis (hello) geklont. Man darf sich ruhig doof anstellen, man muss sich dann nur zu helfen wissen (es führen mehrere Wege nach Rom). Jetzt konnte ich einfach die wichtigsten Dateien aus dem alten Projektverzeichnis in das neue Repository kopieren und dann mit commit und push das noch fast leere Codeberg Repository befüllen.

Irgendwann ist mir dann in der Web-GUI von Codeberg aufgefallen, dass sich da Deteien aufhalten, die da nichts zu suchen haben. Im lokalen Repository sind Artefakte die bei Debugging und ähnlichen Vorgängen anfallen unproblematisch, in einem geteilten Repository möchte man die aber nicht sehen. Das durch das Makefile erstellte hello Executable hatte ich noch bewusst commitet, da ich sie im Archiv eines Releases haben wollte aber ein main Executable welches durch das Debugging von VS Code erzeugt wurde?! Also war es Zeit sich die .gitignore Datei mal zur Brust zu nehmen und dann das Origin aufzuräumen.

Pro Build-Prozess

Der Build mit dem Makefile erzeugte wie oben beschrieben eine hello benannte Datei und es tat das in einem eigenen Verzeichnis (/bin), während VS Code sein Ergebnis direkt neben den Sourcecode im Verzeichnis /src ablegte. Zwar wurde das nicht mehr gepusht, aber abgesehen vom irregulären Pfad und Namen des Binaries, nutzte VS Code eine eigene Toolchain. Das Debugging wurde so mit anderen Einstellungen durchgeführt als das produktive Compilat und das widerstrebte mir zusehends. Ich fragte also meinen Assistenten ob wir es wohl hinbekämen die IDE davon zu überzeugen auch das Makefile und damit meine ausgewählte Toolchain zu verwenden. Das war überraschend einfach und so lernte ich gleich noch etwas über meine bevorzugte IDE. Ich würde in einem neuen Projekt zwar nicht in der Lage sein diese Einstellungen aus dem Kopf selbst vorzunehmen, weiß aber jetzt so viel darüber, dass ich es mit einer kurzen Suche im Web zusammenbekommen sollte (die zwei Dateien aus dem /.vscode Verzeichnis zu kopieren und anzupassen, wäre natürlich die einfachere Option). Statt uns nur auf das Debugging über make zu konzentrieren, haben wir gleich alle Targets des Makefiles auch in VS Code verfügbar gemacht. So ist es jetzt möglich auch eine produktive Binary aus der IDE heraus zu erstellen und in deren Terminal auszuführen.

Dokumentation

Als IT Berater weiß ich wie wichtig eine durchgängige Dokumentation in einem Projekt ist und verwende von der ersten Zeile an Inline-Kommentare. Habe ich eine Funktion fertig oder zumindest funktionell ausgereift, schreibe ich einen DevBlock darüber der übersichtlich darstellt was die Funktion tut und im besten Fall auch wie und warum. Mit wachsendem Projekt ist das aber bald nicht mehr ausreichend alle Aspekte zu dokumentieren. Wenn man versucht Regeln innerhalb des Codes zu dokumentieren, wird es schnell wieder unübersichtlich. Statt jetzt aber jeder Datei oder jedem Thema eine Markdown Datei zuzuordnen, wollte ich mich einmal mit der Wiki Funktionalität von Codeberg auseinandersetzen und so bekam das Projekt ein eigenes Wiki, welches Codeberg praktischerweise auch als Repository bereitstellt. Es wird auf immer mehr Hochzeiten gleichzeitig getanzt. Ich war zunächst geneigt das Wiki in meiner Primärsprache zu verfassen, dachte dann aber "programmiert wird in Englisch" und startete gleich zweisprachig. Ich schreibe in Deutsch und übersetze später in Englische. An diesem Punkt begann ich dann auch die Kommentare im Code zu bereinigen: da waren englische mit deutschen und denglischen Zeilen wild gemischt. Während man die schreibt bemerkt man das offensichtlich nicht in gleichem Maße, wie bei einem späteren Review.

Installation oder Paketierung

Die Installation des Binaries war bereits aus dem Projekt heraus mit make install möglich und eine kleine Änderung in einer IF Abfrage erlaubte es mir auch die Ausgabe für andere Distributionen als das Ubuntu auf dem ich arbeitete zu testen. Aber sollte ich nicht hin und wieder einen Test auf einem echten Debian oder Mint ausführen?! Ich versuche (zum Glück) immer möglichst viel selbst zu machen statt den einfachen Weg über eine Bibliothek zu gehen und so gibt es neben der Standard C Bibliothek keine Abhängigkeiten auf die ich zu achten hätte. Es reicht also das Binary mit SCP auf eine VM zu kopieren und sie kann direkt ausgeführt werden, aber elegant ist das nicht gerade. Als wären da noch nicht genug Hochzeiten zu bedienen, beschaftigte ich mich jetzt auch noch mit Debian Paketen, schaute mir Werkzeuge wie dh_make und dpkg an. Scheiße war das kompliziert - ich wurde mit ganz neuen Fehlermeldungen konfrontiert. Bis ich den Assistenten fragte wie man so etwas am besten anfinge und er mir eine Minimal-Paketierung empfahl: zwei Dateien in zwei Verzeichnissen und ein Kommando als Einzeiler - easy going, und ich sollte dh_make links liegen lassen. Ich habe es dann aber noch verwendet um eine leere Default-Struktur mit Templates für ein Paket zu erstellen. So konnte ich die Paketierung meines Projektes langsam erweitern und wieder etwas lernen.

CI/CD

Continuous integration and continuous delivering (CI/CD) beschreibt einen Prozess der automatisierten Integration und Bereitstellung aus der Entwicklungsumgebung heraus. Zwar braucht ein so kleines Projekt das nicht und ich bin auch nicht ansatzweise in Regionen der Best Practices vorgestoßen wie sie professionelle Entwicklerteams nutzen, eine automatisierte Bereitstellung ist aber sehr sexy um nachvollziehbar und wiederholsicher Software zu erstellen und bereitzustellen. Sie verweigert sich zusätzlichen Installationen die es auf Entwicklungsmaschinen ganz sicher zu Hauf gibt und kann auf eine Toolchain mitsamt festgelegten Versionen festgenagelt werden. Da gibt es bestimmt noch eine weitere Hochzeit auf der man tanzen könnte?!

Ok, an diesem Punkt wäre ich kläglich gescheitert und den Großteil kann ich mir nicht anrechnen. Mehrere Werkzeuge deren Funktionalität man gerade erst zu verstehen beginnt miteinander zu verbinden um dann eine auch noch unbekannte API zu bespielen war vielleicht etwas zu ambitioniert. Die Idee kam zwar von mir, aber die Integration hat der Assistent geschrieben. Meiner Prompts zum Dank ist er dabei aber immer sehr Rücksichtsvoll mit mir und erklärt auch warum das Funktioniert und nicht nur wie. Ich hatte als Grundvoraussetzung für dieses Projekt angegeben jeden einzelnen Schritt nachvollziehen zu können und Geduld ist für ein LLM keine größere Herausforderung. Um den Workflow selbst nachzuvollziehen, schau dir die /.forgejo/workflows/release.yml und das Target deb: im Makefile an. Der Workflow wird automatisch ausgelöst, wenn ein Tag im Repository erzeugt wird welches mit "v" beginnt und vollständig durch die Codeberg Actions ausgeführt. Am Ende des Workflows wird ein neues Release im Repository erstellt und das frisch erstellte deb Paket angehängt. Auf diese Weise kann ich jederzeit Zwischenreleases erstellen, die dann weiterhin nur mit einer Releaseversion benannt sind und nur das Sourcearchiv enthalten.

Der Assistent

Ich habe in den letzten Wochen viele LLMs ausprobiert, einige auch eingehender getestet und ich bin auch noch nicht fertig damit (ein wenig davon ist hier im Blog nachzulesen). Oft beginnt das mit einer einfachen Frage die man kaum als Prompt bezeichnen kann, die aber eine Implikation enthält. Wenn mir die Antwort gefällt oder die Implikation durch nachfragen des LLMs bestätigt wird, melde ich mich bei dem Dienst an und installiere falls verfügbar auch eine App des Anbieters um in einer Free Subscription etwas mehr Token verfügbar zu haben. Claude war das erste Modell eines der großen Mitspieler am Markt und er überraschte mich mit sehr gezielten Nachfragen, ein guter Zuhörer. So war es für mich eine logische Entscheidung ihn als Assistenten ins Projekt zu holen. Claude unterstützt eine eigene Projektstruktur die es nicht nur erlaubt verschiedene Chats zu bündeln, sondern auch Dateien und Vorgaben als Kontext zu hinterlegen. Für dieses Projekt war eine der Vorgaben, dass ich keine fertige Ausgabe wünsche, sondern jeden Schritt nachhaltig verstehen möchte - das es ein Projekt wäre um die Programmiersprache neu zu erlernen und sein Ziel wäre das sicherzustellen. Das hat er während sich die Zahl der Hochzeiten vervielfätigte auch sehr gut befolgt. Würde es sich nicht um eine amerikanische Firma handeln und so der Datenschutz oft dagegen sprechen, würde ich ihn sicher häufiger einbinden.

Fazit

Vielleicht habe ich mit Kanonen auf Spatzen geschossen, aber es ging ja nie wirklich nur darum das vorhandene Bash Skript zu ersetzen. Ich wollte etwas sinnvolles finden um wieder etwas in die C Programmierung zu schauen und habe durch meine Neugier angetrieben, viele weitere Werkzeuge besser kennengelernt oder ganz neu erlernt. Das würde mir sogar in meinem Beruf weiterhelfen, wenngleich die Wahrscheinlichkeit noch einmal so gesund zu werden den wieder auszuüben abwegig erscheint. Sollte sich einmal jemand in mein Repository verlaufen, wird vermutlich das C Programm nicht der Grund dafür sein. Es ist viel mehr Anschauungsobjekt wie sich integrierte Entwicklung im Projektzyklus entwickeln kann und jedes der verwendeten Werkzeuge ist so gut dokumentiert, dass das Repo als Ausgangspunkt eines Tutorials jedes dieser Werkzeuge dienen könnte.

Eine Anekdote zum Ende: Da ich mich innerhalb dieses Projektes so gut zurechtfinde habe ich es für eine "erste Frage" eines anderen großen LLMs genutzt. Aus der Bitte um eine Sicherheitsüberprüfung des Repositories (nicht des Sourcecodes) hat sich ein längeres Gespräch entwickelt und wir haben uns zusammen eine Aufgabe für das LLM auf der Grundlage dieser Diskussion überlegt. Den Arbeitsauftrag bzw. den Prompt dafür hat das LLM dann selbst geschrieben. Es soll ein Review des Entwicklungsprozesses hinter dem Repository werden. Wenn das Hand und Fuß hat, werde ich es hier im Blog veröffentlichen. Ich bin sehr gespannt!

Diskutiert gerne im Fediverse mit mir.

Vorheriger Beitrag