Aufrechterhaltung einer wachsenden, vielfältigen Codebasis mit kontinuierlicher Integration

10

Ich brauche Hilfe bei der Philosophie und beim Design eines kontinuierlichen Integrations-Setups.

Unser aktuelles CI-Setup verwendet Buildbot. Als ich anfing, es zu entwerfen, erbte ich (nun ja, nicht streng, da ich ein Jahr zuvor an seinem Entwurf beteiligt war) einen maßgeschneiderten CI-Builder, der darauf zugeschnitten war, den gesamten Build über Nacht auf einmal auszuführen. Nach einer Weile entschieden wir, dass dies nicht ausreichend war, und begannen, verschiedene CI-Frameworks zu untersuchen und schließlich Buildbot zu wählen. Eines meiner Ziele beim Übergang zum Buildbot (abgesehen davon, dass ich alle Extras genießen konnte) war es, einige der Unzulänglichkeiten unseres maßgeschneiderten nächtlichen Builders zu überwinden.

Humor mich für einen Moment, und lassen Sie mich erklären, was ich geerbt habe. Die Codebasis für mein Unternehmen sind fast 150 einzigartige C ++ - Windows-Anwendungen, von denen jede von einer oder mehreren Dutzend interner Bibliotheken abhängig ist (und viele auch von Bibliotheken von Drittanbietern). Einige dieser Bibliotheken sind voneinander abhängig und haben abhängige Anwendungen, die (obwohl sie nichts miteinander zu tun haben) mit demselben Build dieser Bibliothek erstellt werden müssen. Die Hälfte dieser Anwendungen und Bibliotheken gilt als "Legacy" und nicht portierbar und muss mit mehreren unterschiedlichen Konfigurationen des IBM Compilers (für den ich eindeutige Unterklassen geschrieben habe Compile) erstellt werden. Die andere Hälfte wird mit Visual Studio erstellt.ShellCommands, da VSS nicht unterstützt wird).

Unser ursprünglicher nächtlicher Baumeister hat einfach die Quelle für alles entfernt und Sachen in einer bestimmten Reihenfolge gebaut. Es gab keine Möglichkeit, nur eine einzige Anwendung zu erstellen, eine Revision auszuwählen oder Dinge zu gruppieren. Es würde virtuelle Maschinen starten, um eine Reihe von Anwendungen zu erstellen. Es war nicht sehr robust, es war nicht verteilbar. Es war nicht besonders erweiterbar. Ich wollte in der Lage sein, all diese Einschränkungen in Buildbot zu überwinden.

Die Art und Weise, wie ich dies ursprünglich getan habe, bestand darin, Einträge für jede der Anwendungen zu erstellen, die wir erstellen wollten (alle 150), dann ausgelöste Scheduler zu erstellen, die verschiedene Anwendungen als Gruppen erstellen konnten, und diese Gruppen dann unter einem allgemeinen nächtlichen Build-Scheduler zusammenzufassen. Diese könnten auf dedizierten Slaves ausgeführt werden (keine Schikanen für virtuelle Maschinen mehr), und wenn ich wollte, könnte ich einfach neue Slaves hinzufügen. Wenn wir jetzt einen vollständigen Build außerhalb des Zeitplans ausführen möchten, ist dies nur ein Klick, aber wir können auch nur eine Anwendung erstellen, wenn wir dies wünschen.

Es gibt jedoch vier Schwächen dieses Ansatzes. Eines ist das komplexe Abhängigkeitsnetz unseres Quellbaums. Um die Konfigurationswartung zu vereinfachen, werden alle Builder aus einem großen Wörterbuch generiert. Die Abhängigkeiten werden auf nicht besonders robuste Weise abgerufen und erstellt (nämlich bestimmte Dinge in meinem Build-Target-Wörterbuch abtasten). Das zweite ist, dass jeder Build zwischen 15 und 21 Build-Schritte hat, was in der Weboberfläche schwer zu durchsuchen und zu betrachten ist. Da etwa 150 Spalten vorhanden sind, dauert das Laden ewig (zwischen 30 Sekunden und mehreren Minuten). Drittens haben wir keine automatische Erkennung von Build-Zielen mehr (obwohl ich, so sehr mich einer meiner Kollegen darüber belästigt, nicht sehe, was uns das überhaupt gebracht hat). Schließlich,

Jetzt, da wir uns der neuen Entwicklung zuwenden, beginnen wir, g ++ und Subversion zu verwenden (das alte Repository nicht zu portieren, wohlgemerkt - nur für die neuen Dinge). Außerdem beginnen wir mit mehr Unit-Tests ("mehr" könnte das falsche Bild ergeben ... es ist eher wie bei jedem anderen ) und Integrationstests (mit Python). Es fällt mir schwer herauszufinden, wie ich diese in meine vorhandene Konfiguration integrieren kann.

Also, wo habe ich mich hier philosophisch geirrt? Wie kann ich am besten vorgehen (mit Buildbot - es ist das einzige Puzzleteil, an dem ich arbeiten kann), damit meine Konfiguration tatsächlich gewartet werden kann? Wie gehe ich auf einige Schwächen meines Designs ein? Was funktioniert wirklich in Bezug auf CI-Strategien für große (möglicherweise über-) komplexe Codebasen?

BEARBEITEN:

Ich dachte, ich hätte mein Problem erklärt, aber offensichtlich war ich nicht klar genug. Ich suche keine Vorschläge zum Ändern von CI-Plattformen. Es wird nicht passieren und Antworten, die darauf hindeuten, dass dies nicht akzeptiert wird. Ich möchte wissen, wie andere Leute komplizierte Codebasen mit CI verwalten. Ich habe ein Dutzend verschiedene Produkte im Quadrat , und ich habe Abhängigkeiten, die im Wind verstreut sind, und sie sind alle unterschiedlich. Damit möchte ich wissen, wie ich damit umgehen soll.

Nate
quelle
Auch ich würde gerne die Antwort darauf wissen. Unsere Umgebung ist nicht so komplex wie Ihre, aber wir haben Abhängigkeiten von Abhängigkeiten von Abhängigkeiten (im Fall des Setup-Projekts hat es vier Ebenen tiefe Abhängigkeiten). Ich weiß nicht, ob jedes Projekt ein CI-Projekt sein soll oder ob ich nur die Visual Studio .sln-Datei verwenden soll, um mich darum zu kümmern, damit ich den Abhängigkeitsbaum nicht für jedes Projekt neu erstellen muss (und zukünftiges Projekt).
Moswald
Wenn Sie nicht auf Ihrem lokalen Computer aufbauen können, ist Ihre CI-Server- Mission für Ihr Unternehmen von entscheidender Bedeutung. Vielleicht möchten Sie dies überdenken.
Thorbjørn Ravn Andersen

Antworten:

3

Obwohl ich nicht mit einer so schlimmen Situation konfrontiert war, wie Sie es beschreiben, habe ich eine CI-Konfiguration mit zehn Komponenten beibehalten, zwischen denen es einige "einfache" Abhängigkeiten gibt. Ich hoffe, dass mein Ansatz Ihnen einige Hinweise gibt, mit denen Sie fortfahren können. Das Problem hängt definitiv nicht nur mit der Auswahl des CI-Servers zusammen, sondern auch mit dem gesamten Erstellungsprozess und der Projektstruktur.

Ich werde das Problem in zwei Teile aufteilen: Gebäude und CI.

Gebäude

Für "Erstellen" bedeutet ich den Vorgang des Änderns des vorhandenen Quellcodes in das endgültige Artefakt. Unser Unternehmen verwendet hauptsächlich Java in der Entwicklung, und das von uns verwendete Build-Tool ist Maven. Sie werden das wahrscheinlich aufgrund der Art Ihres Projekts nicht verwenden können, aber es gibt einige wertvolle Konzepte in Maven, die es wert sind, erwähnt zu werden:

1) In der Maven-Welt muss jedes Artefakt (eine Bibliothek, das reale Programm usw.) klar isoliert und entkoppelt sein. Die Abhängigkeiten zwischen Artefakten sollten klar sein. Ich sollte betonen, dass unordentliche Abhängigkeiten, insbesondere zirkuläre Abhängigkeiten zwischen Ihren Build-Artefakten, den Build-Prozess zu einem Chaos machen werden.

Zum Beispiel habe ich zuvor einige Java-Projekte gesehen, obwohl nach dem gesamten Erstellungsprozess mehrere JARs (Sie können es in Java als lib / dll betrachten) erstellt wurden, die tatsächlich voneinander abhängig sind. Wie A.jar verwendet Dinge in B.jar und umgekehrt. Eine solche "Modularisierung" ist völlig bedeutungslos. A.jar und B.jar müssen immer zusammen bereitgestellt und verwendet werden. Die Implikation ist, dass Sie später, wenn Sie sie in verschiedene Projekte aufteilen möchten (z. B. zur Wiederverwendung in anderen Projekten), dies nicht können, da Sie nicht bestimmen können, welches Projekt A oder B zuerst erstellt werden soll.

Ja, es muss im Design Ihrer Software berücksichtigt werden, aber ich bin immer der Meinung, dass ich lieber Zeit damit verbringen würde, das Design neu zu organisieren, um eine zu verwenden, anstatt Zeit dafür zu zahlen, dass komplizierte Bauwerkzeuge / -modelle in einem chaotischen Projekt funktionieren einfaches Gebäudemodell.

2) Abhängigkeiten sollten deklarativ sein. Es gibt viele Build-Prozesse, die ich zuvor gesehen habe, einschließlich aller Bibliotheken, die lokal im Projekt benötigt werden. Dies macht den Erstellungsprozess äußerst mühsam, wenn einige der Bibliotheken tatsächlich andere Artefakte sind, die Sie erstellen müssen.

3) "Zentralisierter" Speicher für die Artefakte zum Abrufen von Abhängigkeiten oder zum Bereitstellen von Artefakten nach dem Kompilieren. Es muss nicht für das gesamte Büro "zentralisiert" sein (es ist großartig, wenn es so ist), nur ein lokales Verzeichnis ist bereits in Ordnung.

Weitere Ausführungen zu 2 und 3. Um nur ein Beispiel zu nennen: Ich bin auf ein Projekt gestoßen, das drei unabhängige Projekte umfasste. Jedes Projekt basiert auf der Quelle sowie den Bibliotheken unter dem Verzeichnis lib / im Quellverzeichnis. Projekt A wird mehrere Bibliotheken erstellen, die wiederum von Projekt B und C verwendet werden. Es gibt einige Nachteile: Die Erstellungsprozedur ist kompliziert und schwieriger zu automatisieren; Die Quellcodeverwaltung wird mit unnötigen doppelten JARs aufgebläht, die in verschiedenen Projekten wiederverwendet werden

In Mavens Welt enthalten Projekt B und C nicht wirklich A.jar (und andere Abhängigkeiten, wie andere Bibliotheken von Drittanbietern) in der Projektquelle. Es ist deklarativ. In der Build-Konfiguration von Projekt B wird beispielsweise einfach deklariert, dass Folgendes erforderlich ist: A.lib v1.0, xyz.lib v2.1 usw., und das Build-Skript sucht die Bibliothek unter /A/1.0/A. jar und /xyz/2.1/xyz.lib

Für Nicht-Maven-Welten muss dieses Artefaktverzeichnis nur ein oder zwei Verzeichnisse mit vereinbarter Verzeichnisstruktur sein. Sie können alle Bibliotheken von Drittanbietern an einem Freigabespeicherort ablegen und Entwickler synchronisieren oder auf ihre lokalen Computer kopieren lassen. In meinen C ++ - Projekten, die ich vor vielen Jahren durchgeführt habe, setze ich die Bibliothek und den Header auf $ {Artefaktverzeichnis} / lib_name / ver und mit Artefakt_ID als Umgebungsvariable.

Wenn Projekt A erstellt wird, enthält es eine Kopie seines Ergebnisses in diesem Artefaktverzeichnis, sodass B beim Erstellen von Projekt B das Ergebnis von A automatisch ohne manuelles Kopieren abrufen kann.

4) Nicht veränderliche Freisetzung. Sobald Sie A.lib 1.0 veröffentlicht haben, können Sie nicht erwarten, dass sich der Inhalt von A.lib 1.0 nach 1 Monat ändert, nur weil es einige Fehlerbehebungen gibt. In diesem Fall sollte es A.lib 1.1 sein. Das Artefakt einer sich ändernden Codebasis sollte eine spezielle "Version" berücksichtigen, für die wir in Maven einen Schnappschuss nennen.

Nicht veränderbare Freisetzung ist eher ein ethisches Problem. Aber was es gelöst hat, ist klar: Wenn Sie viele Projekte haben, die dieselbe Bibliothek verwenden, wissen Sie, welche Version dieser Bibliothek Sie verwenden, und Sie werden sicher sein, dass dieselbe Version der Bibliothek, die in verschiedenen Projekten verwendet wird, tatsächlich dieselbe ist . Ich denke, viele von uns haben das Problem gelöst: Warum verwenden Projekt X und Y beide lib A, aber das Build-Ergebnis ist unterschiedlich? (und es stellt sich heraus, dass die von X und Y verwendete Bibliothek A tatsächlich unterschiedlich ist, nachdem der Inhalt oder die Dateigröße der Bibliothek untersucht wurde).


All dies stellt sicher, dass Ihre Projekte unabhängig und ohne viele manuelle Tricks erstellt werden können. Erstellen Sie zuerst Projekt A, kopieren Sie A.lib in Projekt B und erstellen Sie dann Projekt B ...

Wenn Sie beispielsweise eine Übung wie diese durchlaufen haben, wird beim Erstellen von Projekt A versucht, die Abhängigkeiten aus dem zentralen Artefaktspeicher abzurufen. Falls einige Abhängigkeiten (bei denen es sich um andere Projekte Ihres Unternehmens handelt, z. B. Projekt B) nicht gefunden werden, müssen Sie die Quelle von Projekt B abrufen, es erstellen (das bei Erfolg auf dem zentralen Speicher bereitgestellt wird) und Erstellen Sie dann erneut Projekt A.


CI

Mit einem einfachen Build-System mit klaren Abhängigkeiten wird CI viel einfacher. Ich gehe davon aus, dass Ihr CI-Server die folgenden Anforderungen erfüllt:

1) Überwachen Sie die Quellcodeverwaltung und checken Sie sie nur aus, wenn sich die Quelle ändert

2) Kann Abhängigkeiten zwischen Projekten einrichten

Bei einer eindeutigen Abhängigkeit für Projekte müssen Sie lediglich Projekte in CI gemäß Ihren tatsächlichen Projekten einrichten und die CI-Projektabhängigkeit gemäß der Abhängigkeit Ihrer Projekte einrichten. Ihr CI-Server sollte so eingestellt sein, dass vor dem Erstellen eines Projekts abhängige Projekte erstellt werden (und das Erstellen erfolgt natürlich nur, wenn sich die Projektquelle tatsächlich geändert hat).

Wenn alles gut geht, sollten Sie ein riesiges, komplexes, aber dennoch verwaltbares CI haben (und noch wichtiger, eine überschaubare Projektstruktur).

Adrian Shum
quelle
Ich mag, wohin diese Antwort führt, aber könnten Sie im Abschnitt "Gebäude" näher darauf eingehen? Das heißt, geben Sie einige Beispiele, erklären Sie einige der Konzepte ausführlicher und ausführlicher.
Nate
hm ... wie welcher Teil? Ich versuche, den Beitrag zu bearbeiten, um mehr Details zu jedem Teil zu erhalten. Es ist besser, wenn Sie mir sagen können, auf welchen Teil Sie näher eingehen müssen. Übrigens, wenn Sie auch Java verwenden, schauen Sie in maven.apache.org nach. Maven ist vielleicht nicht die einfachste Sache, aber es zwingt Sie, Ihre Sachen sauber und organisiert zu machen.
Adrian Shum
1

Was hat in ähnlichen Situationen für mich funktioniert:

  • Abstraktion auf App-Ebene durch Umleiten einiger Teile des Builds in dedizierte Build-Apps (in unserem Fall handelt es sich um PHP-Skripte, die vom Haupt-Build aufgerufen werden). Dies kann einige Dutzend Zeilen von Erstellungsschritten zu einem Erstellungsschritt reduzieren.
  • Abstraktion auf Build-Ebene durch Erstellen von Sub-Build-Skripten, die über das Haupt-Build-Skript gestartet werden. Keine Ahnung, ob es für Buildbot relevant ist (keine Erfahrung mit diesem Produkt).

Ich würde vorschlagen, dass Sie den Build selbst als Softwareentwicklungsprojekt behandeln. Dies bedeutet, dass Sie die Build-Codebasis modularisieren und einige automatisierte Tests dafür schreiben müssen (z. B. eine bekannte Versionsnummer erstellen und prüfen, ob sie korrekte Ergebnisse liefert).

Joeri Sebrechts
quelle
0

Ich würde Jenkins als CI-Server betrachten. Sie können sich die Funktionen hier ansehen:

https://wiki.jenkins-ci.org/display/JENKINS/Meet+Jenkins

Warum? Es ist einfach zu installieren, hat eine wunderbare Oberfläche, ist einfach zu konfigurieren und zu erweitern und es gibt eine Menge fertiger Plugins für fast alles:

https://wiki.jenkins-ci.org/display/JENKINS/Plugins

Versuche es :)

Patrizio Rullo
quelle
2
Ich habe das Gefühl, dass Sie den Titel meiner Frage gelesen haben, aber den Text nicht gelesen haben ... Ich suche keinen Produktvorschlag. Ich habe gewählt , ein Produkt. Ich suche nach einer Möglichkeit, ein komplexes CI-Setup zu organisieren.
Nate
Nate, ich habe Ihren ganzen Beitrag gelesen und ich denke, Sie haben das falsche Produkt ausgewählt. Deshalb habe ich Jenkins vorgeschlagen. Ich benutze Jenkins bei der Arbeit für verschiedene Arten von Tests von C ++ - Produkten (sogar Tests, die in mehreren Sprachen geschrieben wurden) mit Clustering auf mehreren Computern und es läuft sehr gut. Es ist sehr einfach zu verwalten und zu konfigurieren. Wirklich, versuchen Sie es :)
Patrizio Rullo
Schauen Sie sich auch diesen Blog-Beitrag an: vperic.blogspot.com/2011/05/…
Patrizio Rullo
0

Wir haben jetzt Go by ThoughtWorks verwendet, bevor wir CruiseControl.Net verwendet haben . Es war nützlich, wenn man bedenkt, dass wir zwei Teams auf der halben Welt voneinander entfernt haben und es einen großen Zeitzonenunterschied zwischen uns gibt.

Wir verwalten mehrere Projekte und die meisten Aufgaben beinhalten Überschneidungen zwischen zwei Entwicklern auf beiden Seiten der Welt. Daher müssen wir berücksichtigen, dass nicht alle Dateien den Build eines anderen beschädigen sollten.

Mit Go ist das Management auch einfacher und da Agile Process eingefleischt ist, hat es uns nach der Installation weniger Kopfschmerzen bereitet. Das Testen wurde auch in Go integriert.

Anarcho
quelle