Wie in der Dokumentation erwähnt, verwendet Gradle ein gerichtetes azyklisches Diagramm (DAG) zum Erstellen ein Abhängigkeitsdiagramm. Aus meiner Sicht ist die Verwendung separater Zyklen zur Evaluierung und Ausführung ein wichtiges Merkmal für ein Build-Tool. z.B. Das Gradle-Dokument gibt an, dass dies einige Funktionen ermöglicht, die sonst nicht möglich wären.
Ich bin an Beispielen aus der Praxis interessiert, die die Leistungsfähigkeit dieser Funktion veranschaulichen. Was sind einige Anwendungsfälle, für die ein Abhängigkeitsdiagramm wichtig ist? Ich interessiere mich besonders für persönliche Geschichten aus dem Feld, ob mit Gradle oder einem ähnlich ausgerüsteten Werkzeug.
Ich mache dieses "Community-Wiki" von Anfang an, da es schwierig sein wird, eine "richtige" Antwort zu finden.
Diese provokative Frage gab die Motivation, endlich Gradle zu untersuchen. Ich habe es immer noch nicht benutzt, daher kann ich nur eine Analyse anbieten, die ich während des Durchsuchens der Dokumente notiert habe, keine persönlichen Geschichten.
Meine erste Frage war, warum ein Gradle-Task-Abhängigkeitsdiagramm garantiert azyklisch ist. Ich habe die Antwort darauf nicht gefunden, aber ein gegenteiliger Fall ist leicht zu konstruieren, daher nehme ich an, dass die zyklische Erkennung eine Validierung ist, die ausgeführt wird, wenn der Graph erstellt wird und der Build vor der Ausführung der ersten Aufgabe fehlschlägt Es gibt illegale zyklische Abhängigkeiten. Ohne zuerst das Diagramm zu erstellen, wird diese Fehlerbedingung möglicherweise erst erkannt, wenn der Build fast abgeschlossen ist. Außerdem müsste die Erkennungsroutine ausgeführt werden, nachdem jeder Task ausgeführt wurde, was sehr ineffizient wäre (solange der Graph inkrementell erstellt wurde und global verfügbar ist, wäre eine Tiefensuche nur erforderlich, um einen Startpunkt zu finden, und anschließend Zyklusauswertungen würden nur minimale Arbeit erfordern, aber die Gesamtarbeit wäre immer noch größer als eine einzige Reduktion auf das gesamte Beziehungsgeflecht von Anfang an. Ich würde die Früherkennung als einen großen Vorteil bezeichnen.
Eine Taskabhängigkeit kann faul sein (siehe: 4.3 Taskabhängigkeiten und ein ähnliches Beispiel in 13.14). Lazy Task-Abhängigkeiten konnten nicht korrekt ausgewertet werden, bis der gesamte Graph erstellt wurde. Dasselbe gilt für die transitive (Nicht-Task-) Abhängigkeitsauflösung, die unzählige Probleme verursachen kann und wiederholte Neukompilierungen erfordert, wenn zusätzliche Abhängigkeiten entdeckt und aufgelöst werden (die auch wiederholte Anforderungen an ein Repository erfordern). Das Aufgabenregelmerkmal (13.8) wäre auch nicht möglich. Diese Probleme und wahrscheinlich viele andere können verallgemeinert werden, wenn man bedenkt, dass Gradle eine dynamische Sprache verwendet und Aufgaben dynamisch hinzufügen und ändern kann, sodass die Ergebnisse vor einer Erstdurchlaufbewertung nicht deterministisch sein können, da der Ausführungspfad erstellt wird während der Laufzeit modifiziert, können somit unterschiedliche Folgen der Auswertung zu beliebig unterschiedlichen Ergebnissen führen, wenn es Abhängigkeiten oder Verhaltensanweisungen gibt, die erst später bekannt werden, weil sie noch nicht erstellt wurden. (Dies kann es wert sein, mit einigen konkreten Beispielen zu untersuchen. Wenn es wahr ist, dann wären sogar zwei Durchgänge nicht immer ausreichend. Wenn A - & gt; B, B - & gt; C, wobei C das Verhalten von A so ändert, dass es hängt nicht mehr von B ab, dann hast du ein Problem. Ich hoffe, es gibt einige Best Practices, um Metaprogrammierung mit nicht-lokalem Umfang zu beschränken, um es nicht in willkürlichen Aufgaben zu erlauben. Ein lustiges Beispiel wäre eine Simulation eines Zeitreise-Paradoxons, wo Ein Enkel tötet seinen Großvater oder heiratet seine Großmutter und illustriert dabei einige praktische ethische Prinzipien!)
Er kann bessere Status- und Fortschrittsberichte für einen aktuell ausgeführten Build ermöglichen. Ein TaskExecutionListener stellt Before / After-Hooks zur Verarbeitung jeder Task bereit, aber ohne die Anzahl der verbleibenden Tasks zu kennen, gibt es nicht viel anderes über den Status als "6 Tasks abgeschlossen. Über die Ausführung von Task foo." Stattdessen können Sie einen TaskExecutionListener mit der Anzahl der Tasks in gradle.taskGraph.whenReady initialisieren und dann an TaskExecutionGraph anhängen. Jetzt könnte es Informationen bereitstellen, um Berichtdetails wie "6 von 72 abgeschlossenen Aufgaben zu aktivieren. Nun Ausführung der Aufgabe foo. Geschätzte verbleibende Zeit: 2h 38m." Das wäre nützlich, um auf einer Konsole für einen Server mit kontinuierlicher Integration angezeigt zu werden, oder wenn Gradle zur Orchestrierung eines großen Builds für mehrere Projekte verwendet wurde und Zeitschätzungen entscheidend waren.
Wie von Jerry Bullard hervorgehoben, ist der Evaluierungsteil des Lebenszyklus entscheidend für die Bestimmung des Ausführungsplans, der Informationen über die Umgebung liefert, da die Umgebung teilweise durch den Ausführungskontext bestimmt wird (Beispiel 4.15 im Abschnitt Konfigurieren durch DAG) ). Außerdem könnte ich sehen, dass dies für die Ausführungsoptimierung nützlich ist. Unabhängige Teilwege konnten sicher an verschiedene Threads übergeben werden. Walking-Algorithmen für die Ausführung können weniger speicherintensiv sein, wenn sie nicht naiv sind (meine Intuition besagt, dass der Pfad mit den meisten Unterpfaden immer zu einem größeren Stapel führt, als immer Pfade mit den wenigsten Unterpfaden bevorzugen).
Eine interessante Verwendung davon könnte eine Situation sein, in der viele Komponenten eines Systems anfänglich ausgegeben werden, um Demos und inkrementelle Entwicklung zu unterstützen. Dann, während der Entwicklung, anstatt die Build-Konfiguration zu aktualisieren, wenn jede Komponente implementiert wird, kann der Build selbst bestimmen, ob ein Teilprojekt bereits bereit für die Aufnahme ist (vielleicht versucht es, den Code zu erfassen, kompiliert und eine vorher festgelegte Testsuite auszuführen). . Wenn dies der Fall ist, würde die Auswertungsstufe dies aufzeigen, und die entsprechenden Aufgaben würden einbezogen, andernfalls werden die Aufgaben für die Stubs ausgewählt. Möglicherweise besteht eine Abhängigkeit von einer Oracle-Datenbank, die noch nicht verfügbar ist, und Sie verwenden in der Zwischenzeit eine eingebettete Datenbank.Sie könnten den Build die Verfügbarkeit überprüfen lassen, wenn möglich transparent wechseln und Ihnen mitteilen, dass er die Datenbanken gewechselt hat, anstatt sie zu sagen. Es könnte viele kreative Anwendungen in diesen Bereichen geben.
Gradle sieht großartig aus. Danke für Ihre Recherche!
Ein Beispiel aus der gleichen Dokumentation veranschaulicht die Stärke dieses Ansatzes Ansatz:
Wie wir später ausführlich beschreiben (Siehe Kapitel 30, Der Build-Lebenszyklus) Gradle hat eine Konfigurationsphase und eine Ausführungsphase. Nach dem Konfigurationsphase Gradle kennt alle Aufgaben, die ausgeführt werden sollen. Gradel bietet Ihnen einen Haken, um davon Gebrauch zu machen Information. Ein Anwendungsfall dafür wäre sei, um zu prüfen, ob die Release-Aufgabe ist Teil der auszuführenden Aufgaben. Abhängig davon können Sie zuweisen verschiedene Werte für einige Variablen.
Mit anderen Worten, Sie können früh in den Build-Prozess eingreifen, um den Kurs nach Bedarf zu ändern. Wenn bereits einige tatsächliche Build-Arbeiten ausgeführt wurden, kann es zu spät sein, sie zu ändern.
Ich evaluiere jetzt verschiedene Build-Systeme und mit grandle habe ich hässlichen Code hinzugefügt, der alle Aufgaben vom Typ "jar" auflistet und sie so ändert, dass jedes jar-Manifest das "Build-Number" -Attribut enthält (welches später verwendet wird) um endgültige Dateinamen zu erstellen):
%Vor%Tags und Links gradle