Dies ist die ursprüngliche Designbegründung für Meson. Die beschriebene Syntax entspricht nicht der veröffentlichten Version
Das wichtigste Werkzeug eines Softwareentwicklers ist der Editor. Wenn Sie mit Programmierern über die von ihnen verwendeten Editoren sprechen, werden Sie meist mit großer Begeisterung und Lob überschüttet. Sie werden hören, wie Emacs das Größte aller Zeiten ist, wie elegant vi ist oder wie die Integrationsfunktionen von Eclipse Sie so viel produktiver machen. Sie können die Begeisterung und Zuneigung spüren, die die Menschen für diese Programme empfinden.
Das zweitwichtigste Werkzeug, noch wichtiger als der Compiler, ist das Build-System.
Diese werden so ziemlich universell verachtet.
Die positivste Aussage, die Sie normalerweise zu Build-Systemen bekommen können (und es kann etwas Überredungskunst erfordern), ist so etwas wie: Nun, es ist ein schreckliches System, aber alle anderen Optionen sind noch schlimmer. Es ist leicht zu verstehen, warum das so ist. Erstens haben gängige kostenlose Build-Systeme schwer verständliche Syntaxen. Sie verwenden größtenteils globale Variablen, die an zufälligen Stellen gesetzt werden, sodass Sie nie wirklich sicher sein können, was eine bestimmte Codezeile bewirkt. Sie tun bei jeder Gelegenheit seltsame und unvorhersehbare Dinge.
Wir wollen dies anhand eines einfachen Beispiels veranschaulichen. Nehmen wir an, wir möchten ein mit GNU Autotools erstelltes Programm unter GDB ausführen. Das Instinktive wäre, einfach gdb programmname auszuführen. Das Problem ist, dass dies funktionieren kann oder auch nicht. In einigen Fällen ist die ausführbare Datei eine Binärdatei, während sie in anderen Fällen ein Wrapper-Shell-Skript ist, das die eigentliche Binärdatei aufruft, die sich in einem versteckten Unterverzeichnis befindet. Die GDB-Aufrufung schlägt fehl, wenn die Binärdatei ein Skript ist, aber sie funktioniert, wenn es keine ist. Der Benutzer muss sich den Typ jeder seiner ausführbaren Dateien merken (was ein Implementierungsdetail des Build-Systems ist), nur um sie debuggen zu können. Mehrere andere solche Schmerzpunkte finden sich in diesem Blogbeitrag.
Angesichts dieser Eigenheiten ist es kein Wunder, dass die meisten Leute nichts mit Build-Systemen zu tun haben wollen. Sie kopieren und fügen einfach Code ein, der (irgendwie) an einer Stelle funktioniert, und hoffen auf das Beste. Sie tun aktiv alles, um das System nicht zu verstehen, weil der bloße Gedanke daran abstoßend ist. Dies bietet auch eine Art inverse Arbeitsplatzsicherheit. Wenn Sie Werkzeug X nicht kennen, gibt es weniger Chancen, dass Sie dafür verantwortlich sind, es in Ihrer Organisation zu verwenden. Stattdessen können Sie sich mit angenehmeren Dingen beschäftigen.
Dies führt zu einem Teufelskreis. Da die Leute die Werkzeuge meiden und sich nicht damit beschäftigen wollen, arbeiten nur wenige an ihrer Verbesserung. Das Ergebnis ist Apathie und Stagnation.
Können wir es besser machen?
Im Grunde ist das Erstellen von C- und C++-Code keine übermäßig schwierige Aufgabe. Tatsächlich ist das Schreiben eines Texteditors wesentlich komplizierter und erfordert mehr Aufwand. Dennoch haben wir viele qualitativ hochwertige Editoren, aber nur wenige Build-Systeme von fragwürdiger Qualität und Benutzerfreundlichkeit.
Also, in der großen Tradition des „Jucken-des-eigenen-Juckens“, beschloss ich, ein wissenschaftliches Experiment durchzuführen. Der Zweck dieses Experiments war es zu erforschen, was es braucht, um ein „gutes“ Build-System zu entwickeln. Welche Art von Syntax wäre für dieses Problem geeignet? Welche Art von Problemen müsste diese Anwendung lösen? Welche Art von Lösungen wären am besten geeignet?
Um den Anfang zu machen, hier ist eine Liste von Anforderungen, die jedes moderne plattformübergreifende Build-System erfüllen muss.
1. Muss einfach zu bedienen sein
Eine der großen Stärken von Python ist die Tatsache, dass es sehr gut lesbar ist. Es ist leicht zu erkennen, was ein bestimmter Codeblock bewirkt. Es ist prägnant, klar und leicht verständlich. Das vorgeschlagene Build-System muss syntaktisch und semantisch sauber sein. Nebeneffekte, globaler Zustand und Wechselbeziehungen müssen minimiert oder, wenn möglich, vollständig eliminiert werden.
2. Muss standardmäßig das Richtige tun
Die meisten Builds werden von Entwicklern durchgeführt, die am Code arbeiten. Daher müssen die Standardeinstellungen auf diesen Anwendungsfall zugeschnitten sein. Zum Beispiel kompiliert das System Objekte ohne Optimierung und mit Debug-Informationen. Es erstellt Binärdateien, die direkt aus dem Build-Verzeichnis ausgeführt werden können, ohne Linker-Tricks, Shell-Skripte oder magische Umgebungsvariablen.
3. Muss etablierte Best Practices durchsetzen
Es gibt wirklich keinen Grund, Quellcode ohne das Äquivalent von -Wall zu kompilieren. Aktivieren Sie es also standardmäßig. Eine andere Art von Best Practice ist die vollständige Trennung von Quell- und Build-Verzeichnissen. Alle Build-Artefakte müssen im Build-Verzeichnis gespeichert werden. Das Schreiben von stray-Dateien im Quellverzeichnis ist unter keinen Umständen gestattet.
4. Muss native Unterstützung für gängige Plattformen bieten
Viele Open-Source-Projekte können auf proprietären Plattformen wie Windows oder OSX verwendet werden. Das System muss native Unterstützung für die Werkzeuge der Wahl auf diesen Plattformen bieten. In der Praxis bedeutet dies native Unterstützung für Visual Studio und XCode. Dass diese IDEs externe Builder-Binärdateien aufrufen, zählt nicht als native Unterstützung.
5. Darf keine Komplexität durch veraltete Plattformen hinzufügen
Die Arbeit an diesem Build-System begann während der Weihnachtsfeiertage 2012. Dies bietet eine natürliche harte Trennlinie vom 24.12.2012. Jede Plattform, jedes Werkzeug oder jede Bibliothek, die zu diesem Zeitpunkt nicht aktiv genutzt wurde, wird explizit nicht unterstützt. Dazu gehören Unixes wie IRIX, SunOS, OSF-1, Ubuntu-Versionen älter als 12/10, GCC-Versionen älter als 4.7 und so weiter. Wenn diese alten Versionen zufällig funktionieren, großartig. Wenn nicht, wird keine einzige Codezeile zum System hinzugefügt, um deren Fehler zu beheben.
6. Muss schnell sein
Die Konfigurationsphase für ein mittelgroßes Projekt darf nicht länger als fünf Sekunden dauern. Das Ausführen des Compile-Befehls für einen vollständig aktualisierten Baum von 1000 Quelldateien darf nicht länger als 0,1 Sekunden dauern.
7. Muss einfach zu bedienende Unterstützung für moderne Softwareentwicklungsfeatures bieten
Ein Beispiel sind vorkompilierte Header. Derzeit bietet kein kostenloses Build-System native Unterstützung dafür. Weitere Beispiele könnten die einfache Integration von Valgrind und Unit-Tests, Testabdeckungsberichte usw. sein.
8. Muss die Überschreibung von Standardwerten erlauben
Manchmal müssen Sie einfach Dateien mit nur bestimmten Compiler-Flags und keinen anderen kompilieren oder Dateien an seltsamen Orten installieren. Das System muss dem Benutzer dies ermöglichen, wenn er es wirklich möchte.
Überblick über die Lösung
Wenn man diese Anforderungen durchgeht, wird deutlich, dass der einzig gangbare Ansatz ungefähr der gleiche ist wie bei CMake: eine domänenspezifische Sprache zur Deklaration des Build-Systems. Aus dieser Deklaration wird eine Konfiguration für das Backend-Build-System generiert. Dies kann ein Makefile, ein Visual Studio- oder XCode-Projekt oder etwas anderes sein.
Der Unterschied zwischen der vorgeschlagenen DSL und bestehenden ist, dass die neue deklarativ ist. Sie versucht auch, auf einer höheren Abstraktionsebene zu arbeiten als bestehende Systeme. Als Beispiel bedeutet die Verwendung externer Bibliotheken in aktuellen Build-Systemen das manuelle Extrahieren und Übergeben von Compiler-Flags und Linker-Flags. Im vorgeschlagenen System erklärt der Benutzer einfach, dass ein bestimmtes Build-Ziel eine bestimmte externe Abhängigkeit verwendet. Das Build-System kümmert sich dann um die Übergabe aller Flags und Einstellungen an die richtigen Stellen. Das bedeutet, dass sich der Benutzer auf seinen eigenen Code konzentrieren kann, anstatt Kommandozeilenargumente von einem Ort zum anderen zu verschieben.
Eine DSL ist mehr Arbeit als der von SCons verfolgte Ansatz, das System als Python-Bibliothek bereitzustellen. Sie ermöglicht es uns jedoch, die Syntax ausdrucksstärker zu gestalten und bestimmte Arten von Fehlern zu vermeiden, z. B. indem bestimmte Objekte wirklich unveränderlich gemacht werden. Das Endergebnis ist wieder dasselbe: weniger Arbeit für den Benutzer.
Das Backend für Unix erfordert etwas mehr Überlegung. Die Standardwahl wäre Make. Es ist jedoch extrem langsam. Bei großen Codebasen dauert es bei Make nicht selten mehrere Minuten, nur um festzustellen, dass nichts getan werden muss. Anstelle von Make verwenden wir Ninja, das extrem schnell ist. Der Backend-Code ist vom Kern abstrahiert, sodass mit relativ geringem Aufwand weitere Backends hinzugefügt werden können.
Beispielcode
Genug Design-Gespräche, kommen wir zum Code. Bevor wir uns die Beispiele ansehen, möchten wir betonen, dass dies in keiner Weise der endgültige Code ist. Es handelt sich um Proof-of-Concept-Code, der im System wie es derzeit existiert (Februar 2013) funktioniert, sich aber jederzeit ändern kann.
Beginnen wir einfach. Hier ist der Code zum Kompilieren einer einzelnen ausführbaren Binärdatei.
project('compile one', 'c')
executable('program', 'prog.c')
Das ist so einfach wie möglich. Zuerst deklarieren Sie den Projektnamen und die verwendeten Sprachen. Dann geben Sie die zu erstellende Binärdatei und ihre Quellen an. Das Build-System erledigt den Rest. Es fügt die richtigen Suffixe hinzu (z. B. '.exe' unter Windows), setzt die Standard-Compiler-Flags usw.
Normalerweise haben Programme mehr als eine Quelldatei. Die Auflistung aller in Funktionsaufrufen kann unhandlich werden. Deshalb unterstützt das System Schlüsselwortargumente. Sie sehen so aus.
project('compile several', 'c')
sourcelist = ['main.c', 'file1.c', 'file2.c', 'file3.c']
executable('program', sources : sourcelist)
Externe Abhängigkeiten sind einfach zu verwenden.
project('external lib', 'c')
libdep = find_dep('extlibrary', required : true)
sourcelist = ['main.c', 'file1.c', 'file2.c', 'file3.c']
executable('program', sources : sourcelist, dep : libdep)
In anderen Build-Systemen müssen Sie manuell die Compile- und Link-Flags von externen Abhängigkeiten zu den Zielen hinzufügen. In diesem System deklarieren Sie einfach, dass extlibrary obligatorisch ist und das generierte Programm dies verwendet. Das Build-System erledigt die gesamte Verkabelung für Sie.
Hier ist eine etwas kompliziertere Definition. Sie sollte trotzdem verständlich sein.
project('build library', 'c')
foolib = shared_library('foobar', sources : 'foobar.c',\
install : true)
exe = executable('testfoobar', 'tester.c', link : foolib)
add_test('test library', exe)
Zuerst erstellen wir eine Shared Library namens foobar. Sie ist als installierbar markiert, sodass das Ausführen von meson install sie in das Bibliotheksverzeichnis installiert (das System weiß, welches, damit der Benutzer sich nicht darum kümmern muss). Dann erstellen wir eine Test-Binärdatei, die gegen die Bibliothek gelinkt ist. Sie wird nicht installiert, sondern stattdessen zur Liste der Unit-Tests hinzugefügt, die mit dem Befehl meson test ausgeführt werden können.
Oben erwähnten wir vorkompilierte Header als ein Feature, das von anderen Build-Systemen nicht unterstützt wird. Hier ist, wie Sie sie verwenden würden.
project('pch demo', 'cxx')
executable('myapp', 'myapp.cpp', pch : 'pch/myapp.hh')
Der Hauptgrund, warum andere Build-Systeme keine PCH-Unterstützung so einfach bereitstellen können, ist, dass sie bestimmte Best Practices nicht durchsetzen. Aufgrund der Funktionsweise von Include-Pfaden ist es unmöglich, eine PCH-Unterstützung bereitzustellen, die immer sowohl mit In-Source- als auch mit Out-of-Source-Builds funktioniert. Die zwingende Trennung von Build- und Quellverzeichnissen macht dies und viele andere Probleme erheblich einfacher.
Holen Sie sich den Code
Der Code für dieses Experiment finden Sie im Meson-Repository. Es sollte beachtet werden, dass es (zum Zeitpunkt des Schreibens) kein Build-System ist. Es ist nur ein Vorschlag dafür. Es funktioniert noch nicht zuverlässig. Sie sollten es wahrscheinlich nicht als Build-System für Ihr Projekt verwenden.
All dies gesagt, hoffe ich, dass dieses Experiment schließlich zu einem vollwertigen Build-System heranwächst. Dafür brauche ich Ihre Hilfe. Kommentare und insbesondere Patches sind mehr als willkommen.
Die Ergebnisse der Suche sind