(Bild von der offiziellen Seite )Buildbot ist, wie der Name schon sagt, ein kontinuierliches Integrationssystem (ci). Es gab bereits
mehrere Artikel über ihn auf dem Habré, aber aus meiner Sicht sind die Vorteile dieses Werkzeugs nicht sehr klar. Außerdem gibt es fast keine Beispiele, was es schwierig macht, die volle Leistungsfähigkeit des Programms zu erkennen. In meinem Artikel werde ich versuchen, diese Mängel auszugleichen, über das interne Gerät Buildbot'a zu sprechen und Beispiele für einige nicht standardmäßige Skripte zu geben.
Gemeinsame Wörter
Derzeit gibt es eine große Anzahl kontinuierlicher Integrationssysteme, und wenn es um eines davon geht, stellen sich ganz logische Fragen im Sinne von "Warum wird es benötigt, wenn Sie bereits einen <Programmname> haben und jeder ihn verwendet?" Ich werde versuchen, eine solche Frage zu Buildbot zu beantworten. Einige der Informationen werden mit vorhandenen Artikeln dupliziert, andere sind in der offiziellen Dokumentation beschrieben, dies ist jedoch für die Konsistenz der Erzählung erforderlich.
Der Hauptunterschied zu anderen kontinuierlichen Integrationssystemen besteht darin, dass Buildbot ein Python-Framework zum Schreiben von ci ist und keine sofort einsatzbereite Lösung. Dies bedeutet, dass Sie zum Verbinden eines Projekts mit Buildbot zunächst ein separates Python-Programm mit dem Buildbot-Framework schreiben müssen, das die für Ihr Projekt erforderliche kontinuierliche Integrationsfunktion implementiert. Dieser Ansatz bietet enorme Flexibilität und ermöglicht es Ihnen, knifflige Testszenarien zu implementieren, die aufgrund von Architekturbeschränkungen für Out-of-Box-Lösungen nicht möglich sind.
Außerdem ist Buildbot kein Dienst, und daher müssen Sie ihn ehrlich in Ihrer Infrastruktur bereitstellen. Hier stelle ich fest, dass das Framework den Ressourcen des Systems sehr treu ist. Dies ist sicherlich nicht C oder C ++, aber Python gewinnt gegen seine Java-Konkurrenten. Hier zum Beispiel der Vergleich des Speicherverbrauchs mit GoCD (und ja, trotz des Namens ist dies ein Java-System):
Buildbot:

GoCD:

Das Bereitstellen und Schreiben eines separaten Testprogramms selbst kann Sie bei dem Gedanken an die Ersteinrichtung traurig machen. Die Skripterstellung wird jedoch durch die schiere Anzahl der integrierten Klassen erheblich vereinfacht. Diese Klassen decken viele Standardoperationen ab, unabhängig davon, ob Änderungen vom Github-Repository abgerufen oder das Projekt mit CMake erstellt werden. Daher sind Standardskripte für kleine Projekte für einige Travis-CI nicht komplizierter als YML-Dateien. Ich werde nicht über die Bereitstellung schreiben, dies wird in bestehenden Artikeln ausführlich behandelt und es gibt dort auch nichts Kompliziertes.
Als nächstes von Buildbot stelle ich fest, dass die Testlogik standardmäßig auf der Seite des ci-Servers implementiert ist. Dies steht im Widerspruch zu dem mittlerweile beliebten Ansatz „Pipeline als Code“, bei dem die Testlogik in einer Datei (wie .travis.yml) beschrieben wird, die sich zusammen mit dem Projektquellcode im Repository befindet, und der ci-Server diese Datei nur liest und ausführt was es sagt. Auch dies ist nur das Standardverhalten. Mit den Funktionen des Buildbot-Frameworks können Sie den beschriebenen Ansatz implementieren, indem Sie das Testskript im Repository speichern. Es gibt sogar eine fertige Lösung -
bb-travis , die versucht, das Beste aus Buildbot und travis-ci herauszuholen. Darüber hinaus werde ich später in diesem Artikel beschreiben, wie Sie etwas Ähnliches wie dieses Verhalten selbst implementieren können.
Buildbot sammelt standardmäßig jedes Commit beim Push. Es mag wie ein kleines unnötiges Merkmal erscheinen, aber für mich ist es im Gegenteil einer der Hauptvorteile geworden. Viele gängige Lösungen (travis-ci, gitlab-ci) bieten überhaupt keine solche Möglichkeit und arbeiten nur mit dem letzten Commit in der Branche. Stellen Sie sich vor, dass Sie während der Entwicklung häufig Commits auswählen müssen. Es wird unangenehm sein, ein nicht funktionierendes Commit durchzuführen, das vom Build-System nicht überprüft wurde, da es zusammen mit einer Reihe von Commits von oben gestartet wurde. Natürlich können Sie in Buildbot nur das letzte Commit erstellen, und dies erfolgt durch Festlegen nur eines Parameters.
Das Framework verfügt über eine recht gute Dokumentation, die alles detailliert beschreibt, von der allgemeinen Architektur bis zu den Richtlinien für die Erweiterung der integrierten Klassen. Selbst mit einer solchen Dokumentation müssen Sie möglicherweise einige Dinge im Quellcode betrachten. Es ist unter der GPL v2-Lizenz vollständig geöffnet und leicht zu lesen. Von den Minuspunkten - die Dokumentation ist nur in englischer Sprache verfügbar, in russischer Sprache gibt es nur sehr wenige Informationen im Netzwerk. Das Tool ist gestern nicht erschienen, mit seiner Hilfe werden
Python ,
Wireshark ,
LLVM und
viele andere bekannte Projekte zusammengestellt. Updates werden veröffentlicht, das Projekt wird von vielen Entwicklern unterstützt, sodass wir über Zuverlässigkeit und Stabilität sprechen können.
(Python Buildbot Homepage)Theormin
Dieser Teil ist im Wesentlichen eine freie Übersetzung des Kapitels der offiziellen Dokumentation zur Architektur des Frameworks. Es zeigt die gesamte Aktionskette vom Empfang von Änderungen durch das ci-System bis zum Senden von Benachrichtigungen über das Ergebnis an Benutzer. Sie haben also Änderungen am Quellcode des Projekts vorgenommen und diese an das Remote-Repository gesendet. Was als nächstes passiert, ist im Bild schematisch dargestellt:
(Bild aus offizieller Dokumentation )Zunächst sollte Buildbot irgendwie herausfinden, dass Änderungen im Repository vorgenommen wurden. Es gibt zwei Hauptmethoden - Webhooks und Umfragen, obwohl niemand verbietet, sich etwas Anspruchsvolleres auszudenken. Im ersten Fall sind in Buildbot die BaseHookHandler-Nachkommenklassen dafür verantwortlich. Es gibt viele vorgefertigte Lösungen, zum Beispiel
GitHubHandler oder
GitoriusHandler . Die Schlüsselmethode in diesen Klassen ist
getChanges () . Die Logik ist äußerst einfach: Sie muss die HTTP-Anforderung in eine Liste von Änderungsobjekten konvertieren.
Für den zweiten Fall benötigen Sie
PollingChangeSource- Nachkommenklassen. Auch hier gibt es vorgefertigte Lösungen wie
GitPoller oder
HgPoller . Die Schlüsselmethode ist
poll () . Es wird mit einer bestimmten Häufigkeit aufgerufen und muss irgendwie eine Liste der Änderungen im Repository erstellen. Im Fall eines Git kann dies ein Aufruf zum Abrufen von Git und ein Vergleich mit dem zuvor gespeicherten Status sein. Wenn die integrierten Funktionen nicht ausreichen, erstellen Sie einfach Ihre eigene Vererbungsklasse und überladen Sie die Methode. Ein Beispiel für die Verwendung von Polling:
c['change_source'] = [changes.GitPoller( repourl = 'git@git.example.com:project', project = 'My Project', branches = True,
Webhook ist noch einfacher zu bedienen, die Hauptsache ist, nicht zu vergessen, es auf der Git-Server-Seite zu konfigurieren. Dies ist nur eine Zeile in der Konfigurationsdatei:
c['www']['change_hook_dialects'] = { 'github': {} }
Im nächsten Schritt werden die Änderungsobjekte in die Scheduler-Objekte (
Scheduler ) eingegeben. Beispiele für integrierte Scheduler:
AnyBranchScheduler ,
NightlyScheduler ,
ForceScheduler usw. Jeder Scheduler empfängt alle Änderungsobjekte als Eingabe, wählt jedoch nur diejenigen aus, die den Filter bestehen. Der Filter wird über das Argument
change_filter an den Scheduler im Konstruktor
übergeben . Am Ausgang erstellen die Planer Build-Anforderungen. Der Scheduler wählt die Builder basierend auf dem Builder-Argument aus.
Einige Planer haben ein kniffliges Argument namens
treeStableTimer . Dies funktioniert wie folgt: Wenn eine Änderung eingeht, erstellt der Scheduler nicht sofort eine neue Build-Anforderung, sondern startet einen Timer. Wenn neue Änderungen eintreffen und der Timer nicht abgelaufen ist, wird die alte Änderung durch eine neue ersetzt und der Timer aktualisiert. Wenn der Timer endet, erstellt der Scheduler nur eine Build-Anforderung aus der zuletzt gespeicherten Änderung.
Somit ist die Logik implementiert, nur das letzte Commit beim Push zusammenzusetzen. Beispiel für die Scheduler-Konfiguration:
c['schedulers'] = [schedulers.AnyBranchScheduler( name = 'My Scheduler', treeStableTimer = None, change_filter = util.ChangeFilter(project = 'My Project'), builderNames = ['My Builder'] )]
Build-Anforderungen, so seltsam sie auch klingen mögen, gehen an die Eingabe der Builder. Die Aufgabe des Sammlers besteht darin, die Assembly auf einem zugänglichen „Worker“ auszuführen. Worker ist eine Build-Umgebung wie Stretch64 oder Ubuntu1804x64. Die Liste der Arbeiter wird durch das
Arbeiterargument geleitet . Alle Arbeiter in der Liste sollten gleich sein (d. H. Die Namen sind natürlich unterschiedlich, aber die Umgebung im Inneren ist die gleiche), da der Sammler frei ist, einen der verfügbaren auszuwählen. Das Festlegen mehrerer Werte dient hier dazu, die Last auszugleichen und nicht in unterschiedlichen Umgebungen zu bauen. Mit dem Argument
Faktor y erhält der Kollektor eine Folge von Schritten zum Erstellen des Projekts. Ich werde weiter unten ausführlich darüber schreiben.
Beispiel für die Konfiguration des Kollektors
c['builders'] = [util.BuilderConfig( name = 'My Builder', workernames = ['stretch32'], factory = factory )]
Das Projekt ist also fertig. Der letzte Schritt von Buildbot besteht darin, den Build zu benachrichtigen. Reporterklassen sind dafür verantwortlich. Ein klassisches Beispiel ist die
MailNotifier- Klasse, die eine E-Mail mit Build-Ergebnissen sendet.
Beispiel für eine MailNotifier- Verbindung:
c['services'] = [reporters.MailNotifier( fromaddr = 'buildbot@example.com', relayhost = 'mail.example.com', smtpPort = 25, extraRecipients = ['devel@example.com'], sendToInterestedUsers = False )]
Nun, es ist Zeit, zu vollwertigen Beispielen überzugehen. Ich stelle fest, dass Buildbot selbst mit dem Twisted-Framework geschrieben wurde und daher die Kenntnis des Buildbot-Skripts das Schreiben und Verstehen von Buildbot-Skripten erheblich erleichtert. Wir werden einen Prügelknaben für ein Projekt namens Pet Project haben. Lassen Sie es in C ++ schreiben, mit CMake zusammenstellen, und der Quellcode liegt im Git-Repository. Wir waren nicht zu faul und haben Tests für ihn geschrieben, die vom ctest-Team durchgeführt werden. Zuletzt haben wir diesen Artikel gelesen und festgestellt, dass wir das frisch gewonnene Wissen auf unser Projekt anwenden möchten.
Beispiel eins: damit es funktioniert
Eigentlich ist die Konfigurationsdatei:
100 Zeilen Python-Code from buildbot.plugins import *
Durch das Schreiben dieser Zeilen erhalten wir eine automatische Assemblierung beim Pushing in das Repository, ein schönes Webface, E-Mail-Benachrichtigungen und andere Attribute eines sich selbst respektierenden ci. Das meiste davon sollte klar sein: Die Einstellungen der Scheduler, Collectors und anderer Objekte werden ähnlich wie in den zuvor angegebenen Beispielen vorgenommen. Der Wert der meisten Parameter ist intuitiv. Im Detail werde ich mich nur auf die Schaffung einer Fabrik konzentrieren, was ich früher versprochen habe.
Die Factory besteht aus
Build-Schritten , die Buildbot für das Projekt ausführen muss. Wie bei anderen Klassen gibt es viele vorgefertigte Lösungen. Unsere Fabrik besteht aus fünf Schritten. In der Regel besteht der erste Schritt darin, den aktuellen Status des Repositorys abzurufen. Hier machen wir keine Ausnahme. Dazu verwenden wir die Standard-
Git- Klasse:
Erster Schritt factory = util.BuildFactory() factory.addStep(steps.Git( repourl = util.Property('repository'), workdir = 'sources', haltOnFailure = True, submodules = True, progress = True) )
Als nächstes müssen wir ein Verzeichnis erstellen, in dem das Projekt zusammengestellt wird - wir werden einen vollständigen Build aus dem Quellcode erstellen. Zuvor müssen Sie daran denken, das Verzeichnis zu löschen, falls es bereits vorhanden ist. Daher müssen wir zwei Befehle ausführen. Die
ShellSequence- Klasse hilft uns dabei:
Zweiter Schritt factory.addStep(steps.ShellSequence( name = 'create builddir', haltOnFailure = True, hideStepIf = lambda results, s: results == util.SUCCESS, commands = [ util.ShellArg(command = ['rm', '-rf', 'build']), util.ShellArg(command = ['mkdir', 'build']) ]) )
Jetzt müssen Sie CMake starten. Dazu ist es logisch, eine von zwei Klassen zu verwenden -
ShellCommand oder
CMake . Wir werden letzteres verwenden, aber die Unterschiede sind minimal: Es ist ein
einfacher Wrapper über die erste Klasse, was es etwas bequemer macht, CMake-spezifische Argumente zu übergeben.
Dritter Schritt factory.addStep(steps.CMake( workdir = 'build', path = '../sources', haltOnFailure = True) )
Zeit, das Projekt zu kompilieren. Wie im vorherigen Fall können Sie
ShellCommand verwenden . Ebenso gibt es die
Compile- Klasse, die einen Wrapper über
ShellCommand darstellt . Dies ist jedoch ein schwierigerer Wrapper: Die
Compile- Klasse überwacht Warnungen während der Kompilierung und zeigt sie genau in einem separaten Protokoll an. Deshalb werden wir die
Compile- Klasse verwenden:
Vierter Schritt factory.addStep(steps.Compile( name = 'build project', workdir = 'build', haltOnFailure = True, warnOnWarnings = True, command = ['make']) )
Führen Sie zum Schluss unsere Tests durch. Hier verwenden wir die
zuvor erwähnte
ShellCommand- Klasse:
Fünfter Schritt factory.addStep(steps.ShellCommand( name = 'run tests', workdir = 'build', haltOnFailure = True, command = ['ctest']) )
Beispiel zwei: Pipeline als Code
Hier werde ich zeigen, wie eine Budgetoption zum Speichern der Testlogik zusammen mit dem Projektquellcode und nicht in der ci-Server-Konfigurationsdatei implementiert wird.
Fügen Sie dazu die
.buildbot- Datei mit dem Code in das Repository ein, in dem jede Zeile aus Wörtern besteht, von denen die erste als Verzeichnis für die Ausführung des Befehls und der Rest als Befehl mit ihren Argumenten interpretiert wird. Für unser Haustierprojekt
sieht die
.buildbot- Datei folgendermaßen aus:
.Buildbot-Datei mit Befehlen. rm -rf build
. mkdir build
build cmake ../sources
build make
build ctest
Jetzt müssen wir die Buildbot-Konfigurationsdatei ändern. Um die
.buildbot- Datei zu analysieren, müssen wir eine Klasse unseres eigenen Schritts schreiben. In diesem Schritt wird die
.buildbot- Datei gelesen.
Anschließend wird für jede Zeile der
ShellCommand- Schritt mit den erforderlichen Argumenten
hinzugefügt . Um Schritte dynamisch hinzuzufügen, verwenden wir die Methode
build.addStepsAfterCurrentStep () . Es sieht überhaupt nicht beängstigend aus:
Klasse AnalyseSchritt class AnalyseStep(ShellMixin, BuildStep): def __init__(self, workdir, **kwargs): kwargs = self.setupShellMixin(kwargs, prohibitArgs = ['command', 'workdir', 'want_stdout']) BuildStep.__init__(self, **kwargs) self.workdir = workdir @defer.inlineCallbacks def run(self): self.stdio_log = yield self.addLog('stdio') cmd = RemoteShellCommand( command = ['cat', '.buildbot'], workdir = self.workdir, want_stdout = True, want_stderr = True, collectStdout = True ) cmd.useLog(self.stdio_log) yield self.runCommand(cmd) if cmd.didFail(): defer.returnValue(util.FAILURE) results = [] for row in cmd.stdout.splitlines(): lst = row.split() dirname = lst.pop(0) results.append(steps.ShellCommand( name = lst[0], command = lst, workdir = dirname ) ) self.build.addStepsAfterCurrentStep(results) defer.returnValue(util.SUCCESS)
Dank dieses Ansatzes ist die Fabrik für den Sammler einfacher und vielseitiger geworden:
Factory zum Analysieren der .buildbot-Datei factory = util.BuildFactory() factory.addStep(steps.Git( repourl = util.Property('repository'), workdir = 'sources', haltOnFailure = True, submodules = True, progress = True, mode = 'incremental') ) factory.addStep(AnalyseStep( name = 'Analyse .buildbot file', workdir = 'sources', haltOnFailure = True, hideStepIf = lambda results, s: results == util.SUCCESS) )
Beispiel drei: Arbeiter als Code
Stellen Sie sich nun vor, dass wir neben dem Projektcode nicht die Reihenfolge der Befehle bestimmen müssen, sondern die Umgebung für die Assembly. In der Tat definieren wir Arbeiter.
Die .buildbot- Datei könnte
ungefähr so aussehen:
.Buildbot-Umgebungsdatei{
"workers": ["stretch32", "wheezy32"]
}
In diesem Fall wird die Buildbot-Konfigurationsdatei komplizierter, da die Assemblys in verschiedenen Umgebungen miteinander verbunden werden sollen (wenn mindestens eine Umgebung ausfällt, wurde das gesamte Commit als nicht funktionsfähig angesehen). Zwei Ebenen helfen uns, das Problem zu lösen. Wir werden einen lokalen Mitarbeiter haben, der die
.buildbot- Datei analysiert und die Builds für die gewünschten Mitarbeiter
ausführt . Zunächst schreiben wir wie im vorherigen Beispiel unseren Schritt zur Analyse der
.buildbot- Datei. Um die Assembly für einen bestimmten Worker zu starten, werden ein Bundle aus dem
Trigger- Schritt und eine spezielle Art von
TriggerableScheduler- Schedulern
verwendet . Unser Schritt ist etwas komplizierter, aber durchaus verständlich geworden:
Klasse AnalyseSchritt class AnalyseStep(ShellMixin, BuildStep): def __init__(self, workdir, **kwargs): kwargs = self.setupShellMixin(kwargs, prohibitArgs = ['command', 'workdir', 'want_stdout']) BuildStep.__init__(self, **kwargs) self.workdir = workdir @defer.inlineCallbacks def _getWorkerList(self): cmd = RemoteShellCommand( command = ['cat', '.buildbot'], workdir = self.workdir, want_stdout = True, want_stderr = True, collectStdout = True ) cmd.useLog(self.stdio_log) yield self.runCommand(cmd) if cmd.didFail(): defer.returnValue([])
Wir werden diesen Schritt auf den lokalen Arbeiter anwenden. Bitte beachten Sie, dass wir das Tag auf unseren Sammler "Pet Project Builder" gesetzt haben. Damit können wir
MailNotifier filtern und
festlegen , dass Briefe nur an bestimmte Sammler gesendet werden sollen. Wenn diese Filterung nicht durchgeführt wird, erhalten wir beim Erstellen des Commits in zwei Umgebungen drei Buchstaben.
Allgemeiner Sammler factory = util.BuildFactory() factory.addStep(steps.Git( repourl = util.Property('repository'), workdir = 'sources', haltOnFailure = True, submodules = True, progress = True, mode = 'incremental') ) factory.addStep(AnalyseStep( name = 'Analyse .buildbot file', workdir = 'sources', haltOnFailure = True, hideStepIf = lambda results, s: results == util.SUCCESS) ) c['builders'] = [util.BuilderConfig( name = 'Pet Project Builder', tags = ['generic_builder'], workernames = ['local'], factory = factory )]
Wir müssen nur die Sammler und die gleichen auslösbaren Scheduler für alle unsere echten Arbeiter hinzufügen:
Sammler in der richtigen Umgebung for worker in allWorkers: c['schedulers'].append(schedulers.Triggerable( name = 'Pet Project ({}) Scheduler'.format(worker), builderNames = ['Pet Project ({}) Builder'.format(worker)]) ) c['builders'].append(util.BuilderConfig( name = 'Pet Project ({}) Builder'.format(worker), workernames = [worker], factory = specific_factory) )
(Seite unseres Projekts in zwei Umgebungen erstellen)Beispiel 4: Ein Buchstabe pro mehrere Commits
Wenn Sie eines der obigen Beispiele verwenden, können Sie eine unangenehme Funktion feststellen. Da für jedes Commit ein Buchstabe erstellt wird, erhalten wir 20 Buchstaben, wenn wir den Zweig mit 20 neuen Commits verschieben. Wenn Sie dies vermeiden, wie im vorherigen Beispiel, helfen wir auf zwei Ebenen. Wir müssen auch die Klasse ändern, um die Änderungen zu erhalten. Anstatt viele Änderungsobjekte zu erstellen, erstellen wir nur ein solches Objekt, in dessen Eigenschaften eine Liste aller Commits übertragen wird. In Eile kann dies folgendermaßen geschehen:
Klasse MultiGitHubHandler class MultiGitHubHandler(GitHubHandler): def getChanges(self, request): new_changes = GitHubHandler.getChanges(self, request) if not new_changes: return ([], 'git') change = new_changes[-1] change['revision'] = '{}..{}'.format( new_changes[0]['revision'], new_changes[-1]['revision']) commits = [c['revision'] for c in new_changes] change['properties']['commits'] = commits return ([change], 'git') c['www']['change_hook_dialects'] = { 'base': { 'custom_class': MultiGitHubHandler } }
Um mit solch einem ungewöhnlichen Änderungsobjekt arbeiten zu können, benötigen wir unseren eigenen speziellen Schritt, der dynamisch Schritte erstellt, die ein bestimmtes Commit erfassen:
Klasse GenerateCommitSteps class GenerateCommitSteps(BuildStep): def run(self): commits = self.getProperty('commits') results = [] for commit in commits: results.append(steps.Trigger( name = 'Checking commit {}'.format(commit), schedulerNames = ['Pet Project Commits Scheduler'], waitForFinish = True, haltOnFailure = True, warnOnWarnings = True, sourceStamp = { 'branch': util.Property('branch'), 'revision': commit, 'codebase': util.Property('codebase'), 'repository': util.Property('repository'), 'project': util.Property('project') } ) ) self.build.addStepsAfterCurrentStep(results) return util.SUCCESS
Fügen Sie unseren gemeinsamen Sammler hinzu, der nur für die Ausführung von Assemblys einzelner Commits zuständig ist. Es sollte markiert werden, um dann das Senden von Briefen nach diesem Tag selbst zu filtern.
General Mail Fetcher c['schedulers'] = [schedulers.AnyBranchScheduler( name = 'Pet Project Branches Scheduler', treeStableTimer = None, change_filter = util.ChangeFilter(project = 'Pet Project'), builderNames = ['Pet Project Branches Builder'] )] branches_factory = util.BuildFactory() branches_factory.addStep(GenerateCommitSteps( name = 'Generate commit steps', haltOnFailure = True, hideStepIf = lambda results, s: results == util.SUCCESS) ) c['builders'] = [util.BuilderConfig( name = 'Pet Project Branches Builder', tags = ['branch_builder'], workernames = ['local'], factory = branches_factory )]
Es bleibt nur der Kollektor für einzelne Commits hinzuzufügen. Wir kennzeichnen diesen Sammler nur nicht mit einem Tag, daher werden keine Buchstaben dafür erstellt.
General Mail Fetcher c['schedulers'].append(schedulers.Triggerable( name = 'Pet Project Commits Scheduler', builderNames = ['Pet Project Commits Builder']) ) c['builders'].append(util.BuilderConfig( name = 'Pet Project Commits Builder', workernames = ['stretch32'], factory = specific_factory) )
Letzte Worte
Dieser Artikel ersetzt in keiner Weise das Lesen der offiziellen Dokumentation. Wenn Sie also an Buildbot interessiert sind, sollten Sie ihn als Nächstes lesen. Vollversionen der Konfigurationsdateien aller Beispiele sind auf dem
Github verfügbar. Verwandte Links, von denen die meisten Materialien für den Artikel stammen:
- Offizielle Dokumentation
- Projektquellcode