(صورة من الموقع الرسمي )Buildbot ، كما يوحي الاسم ، هو نظام تكامل مستمر (ci). كان هناك بالفعل
العديد من المقالات عنه حول Habré ، لكن من وجهة نظري ، فإن مزايا هذه الأداة ليست واضحة جدًا منها. بالإضافة إلى ذلك ، ليس لديهم أي أمثلة تقريبًا ، مما يجعل من الصعب رؤية القوة الكاملة للبرنامج. في مقالي ، سأحاول التعويض عن أوجه القصور هذه ، والتحدث عن الجهاز الداخلي Buildbot'a وإعطاء أمثلة على العديد من البرامج النصية غير القياسية.
كلمات شائعة
حاليًا ، يوجد عدد كبير من أنظمة التكامل المستمر ، وعندما يتعلق الأمر بأحدها ، تثور أسئلة منطقية تمامًا بروح "لماذا هناك حاجة إذا كان لديك بالفعل اسم <program_name> ويستخدمه الجميع؟" سأحاول الإجابة على هذا السؤال حول Buildbot. سيتم ازدواجية بعض المعلومات مع المقالات الموجودة ، وبعضها موصوف في الوثائق الرسمية ، ولكن هذا ضروري لتناسق السرد.
الفرق الرئيسي عن أنظمة التكامل المستمر الأخرى هو أن Buildbot هو إطار بيثون لكتابة ci ، وليس حلاً خارج الصندوق. هذا يعني أنه من أجل توصيل مشروع بـ Buildbot ، يجب عليك أولاً كتابة برنامج بيثون منفصل باستخدام إطار عمل Buildbot الذي يقوم بتنفيذ وظائف التكامل المستمر التي يحتاجها مشروعك. يوفر هذا النهج مرونة هائلة ، مما يسمح لك بتنفيذ سيناريوهات اختبار صعبة مستحيلة للحلول خارج الصندوق بسبب القيود المعمارية.
علاوة على ذلك ، Buildbot ليست خدمة ، وبالتالي يجب عليك نشرها بصدق على البنية التحتية الخاصة بك. هنا ألاحظ أن الإطار مخلص للغاية لموارد النظام. هذا بالتأكيد ليس C أو C ++ ، ولكن الثعبان يفوز ضد منافسي جافا. هنا ، على سبيل المثال ، مقارنة استهلاك الذاكرة مع GoCD (ونعم ، على الرغم من الاسم ، هذا نظام جافا):
Buildbot:

GoCD:

إن نشر برنامج اختبار منفصل وكتابته يمكن أن يجعلك حزينًا عند التفكير في الإعداد الأولي. ومع ذلك ، يتم تبسيط البرمجة النصية إلى حد كبير من خلال العدد الهائل من الفصول المدمجة. تغطي هذه الفئات العديد من العمليات القياسية ، سواء كانت تحصل على تغييرات من مستودع جيثب أو بناء المشروع مع CMake. نتيجةً لذلك ، لن تكون البرامج النصية القياسية للمشاريع الصغيرة أكثر تعقيدًا من ملفات YML لبعض الترافيز. لن أكتب عن النشر ، هذا يتم تغطيته بالتفصيل في المقالات الحالية وليس هناك شيء معقد أيضًا.
الميزة التالية في Buildbot ، ألاحظ أنه يتم افتراضيًا تنفيذ منطق الاختبار على جانب خادم ci. وهذا يتعارض مع نهج "خط الأنابيب ككود" الشائع الآن ، والذي يتم فيه وصف منطق الاختبار في ملف (مثل .travis.yml) يكمن في المستودع إلى جانب شفرة مصدر المشروع ، وقراءة خادم ci فقط هذا الملف وينفّذ ماذا تقول. مرة أخرى ، هذا هو السلوك الافتراضي فقط. تسمح لك إمكانيات إطار Buildbot بتطبيق الطريقة الموضحة مع تخزين البرنامج النصي للاختبار في المستودع. يوجد حتى حل جاهز -
bb-travis ، والذي يحاول الاستفادة بشكل أفضل من Buildbot و travis-ci. بالإضافة إلى ذلك ، في وقت لاحق من هذه المقالة سوف أصف كيفية تنفيذ شيء مشابه لهذا السلوك بنفسي.
Buildbot افتراضيا يجمع كل التزام عند الدفع. قد يبدو مثل بعض الميزات الصغيرة غير الضرورية ، ولكن بالنسبة لي ، على العكس من ذلك ، فقد أصبحت واحدة من المزايا الرئيسية. لا توفر العديد من الحلول الشائعة خارج الصندوق (travis-ci ، gitlab-ci) مثل هذه الفرصة على الإطلاق ، حيث تعمل فقط مع الالتزام الأخير على الفرع. تخيل أنه خلال عملية التطوير ، غالبًا ما يتعين عليك اختيار تعهدات الكرز. سيكون من غير اللطيف اتخاذ التزام غير عامل ، والذي لم يتم التحقق منه بواسطة نظام الإنشاء بسبب حقيقة أنه تم إطلاقه مع مجموعة من التعهدات من الأعلى. بالطبع ، في Buildbot يمكنك فقط إنشاء الالتزام الأخير ، ويتم ذلك عن طريق تعيين معلمة واحدة فقط.
يحتوي الإطار على وثائق جيدة إلى حد ما ، والتي تصف كل شيء بالتفصيل من الهيكل العام إلى المبادئ التوجيهية لتوسيع الفئات المدمجة. ومع ذلك ، حتى مع مثل هذه الوثائق ، قد تضطر إلى النظر إلى بعض الأشياء في التعليمات البرمجية المصدر. إنه مفتوح بالكامل تحت رخصة GPL v2 وسهل القراءة. من السلبيات - الوثائق متوفرة فقط باللغة الإنجليزية ، باللغة الروسية هناك القليل جدا من المعلومات على الشبكة. لم تظهر الأداة بالأمس ، حيث تم تجميع
بيثون مساعدتها و
Wireshark و
LLVM والعديد من المشروعات المعروفة الأخرى. يتم إصدار التحديثات ، ويدعم المشروع العديد من المطورين ، حتى نتمكن من التحدث عن الموثوقية والاستقرار.
(بيثون Buildbot الصفحة الرئيسية)ثورمين
هذا الجزء هو في الأساس ترجمة مجانية لفصل الوثائق الرسمية حول بنية الإطار. يعرض السلسلة الكاملة من الإجراءات بدءًا من تلقي التغييرات بواسطة نظام ci إلى إرسال إعلامات بالنتيجة إلى المستخدمين. لذلك ، قمت بإجراء تغييرات على الكود المصدري للمشروع وأرسلتهم إلى المستودع البعيد. ما يحدث بعد ذلك يتم عرضه بشكل تخطيطي في الصورة:
(صورة من الوثائق الرسمية )بادئ ذي بدء ، يجب أن يكتشف Buildbot بطريقة ما أن هناك تغييرات في المستودع. هناك طريقتان رئيسيتان - مواقع الويب واستطلاعات الرأي ، على الرغم من أن لا أحد يحظر الخروج بشيء أكثر تطوراً. في الحالة الأولى ، في Buildbot ، تكون فئات سلال BaseHookHandler مسؤولة عن هذا. هناك العديد من الحلول الجاهزة ، على سبيل المثال ،
GitHubHandler أو
GitoriusHandler . الطريقة الأساسية في هذه الفئات هي
getChanges () . منطقه بسيط للغاية: يجب أن يحول طلب HTTP إلى قائمة كائنات التغيير.
للحالة الثانية ، تحتاج إلى
فئات هبوط PollingChangeSource . مرة أخرى ، هناك حلول جاهزة ، مثل
GitPoller أو
HgPoller . الطريقة الرئيسية هي
الاستطلاع () . يتم استدعاؤه بتردد معين ويجب أن ينشئ بطريقة ما قائمة بالتغييرات في المستودع. في حالة git ، قد تكون هذه دعوة إلى git fetch ومقارنة بالحالة المحفوظة السابقة. إذا لم تكن القدرات المدمجة كافية ، فقم فقط بإنشاء صف الوراثة الخاص بك وإفراط في تحميل الأسلوب. مثال على استخدام الاقتراع:
c['change_source'] = [changes.GitPoller( repourl = 'git@git.example.com:project', project = 'My Project', branches = True,
Webhook أسهل في الاستخدام ، والشيء الرئيسي هو عدم نسيان تكوينه على جانب خادم git. هذا سطر واحد فقط في ملف التكوين:
c['www']['change_hook_dialects'] = { 'github': {} }
الخطوة التالية ، كائنات التغيير هي مدخلات لكائنات
المجدول (
المجدول ). أمثلة على البرامج المضمنة المضمنة:
AnyBranchScheduler و
NightlyScheduler و
ForceScheduler ، إلخ. يستقبل كل مجدول جميع كائنات التغيير كمدخلات ، لكنه يختار فقط تلك الكائنات التي تتجاوز المرشح. يتم تمرير المرشح إلى المجدول في المُنشئ عن طريق وسيطة
change_filter . عند الإخراج ، يقوم المخططون بإنشاء طلبات الإنشاء. يختار المجدول البناة بناءً على وسيطة البناة.
بعض المخططين لديهم حجة صعبة تسمى
treeStableTimer . يعمل كما يلي: عند تلقي تغيير ، لا يقوم المجدول بإنشاء طلب إنشاء جديد فورًا ، ولكنه يبدأ مؤقتًا. إذا وصلت تغييرات جديدة ولم ينتهي الموقت ، فسيتم استبدال التغيير القديم بتغيير جديد ، ويتم تحديث المؤقت. عندما ينتهي المؤقت ، ينشئ المجدول طلب بناء واحد فقط من آخر تغيير تم حفظه.
وبالتالي ، يتم تطبيق منطق تجميع الالتزام الأخير فقط عند الدفع. مثال تكوين المجدول:
c['schedulers'] = [schedulers.AnyBranchScheduler( name = 'My Scheduler', treeStableTimer = None, change_filter = util.ChangeFilter(project = 'My Project'), builderNames = ['My Builder'] )]
طلبات البناء ، على الرغم من أنها قد تبدو غريبة ، انتقل إلى مدخلات منشئي. تتمثل مهمة المجمع في تشغيل التجميع على "عامل" يمكن الوصول إليه. العامل هو بيئة بناء ، مثل stretch64 أو ubuntu1804x64. يتم تمرير قائمة العمال من خلال حجة
العمال . يجب أن يكون جميع العمال في القائمة متماثلين (أي أن الأسماء مختلفة بشكل طبيعي ، ولكن البيئة داخلها هي نفسها) ، نظرًا لأن المجمع له حرية اختيار أيٍ من الأسماء المتاحة. يؤدي تعيين قيم متعددة هنا إلى موازنة التحميل وليس الإنشاء في بيئات مختلفة. باستخدام الوسيطة
factor y ، يتلقى المجمّع سلسلة من الخطوات لإنشاء المشروع. سأكتب عن هذا بالتفصيل أدناه.
مثال على تكوين المجمع:
c['builders'] = [util.BuilderConfig( name = 'My Builder', workernames = ['stretch32'], factory = factory )]
المشروع جاهز الخطوة الأخيرة لـ Buildbot هي إخطار الإنشاء. الطبقات مراسل هي المسؤولة عن هذا. مثال كلاسيكي هو فئة
MailNotifier ، التي ترسل رسالة بريد إلكتروني بنتائج البناء.
مثال اتصال
MailNotifier :
c['services'] = [reporters.MailNotifier( fromaddr = 'buildbot@example.com', relayhost = 'mail.example.com', smtpPort = 25, extraRecipients = ['devel@example.com'], sendToInterestedUsers = False )]
حسنًا ، لقد حان الوقت للانتقال إلى أمثلة كاملة. لاحظت أن Buildbot نفسه كتب باستخدام إطار Twisted ، وبالتالي فإن الإلمام به سيسهل إلى حد كبير كتابة وفهم نصوص Buildbot. سيكون لدينا صبي سوط لمشروع يسمى مشروع الحيوانات الأليفة. دعه يكتب في C ++ ، يتم تجميعه باستخدام CMake ، ويوجد الكود المصدري في مستودع git. لم نكن كسولين جدًا وكتبنا اختبارات له يديرها فريق ctest. في الآونة الأخيرة ، قرأنا هذا المقال وأدركنا أننا نريد تطبيق المعرفة التي تم الحصول عليها حديثًا على مشروعنا.
مثال واحد: من أجل أن تعمل
في الواقع ، ملف التكوين:
100 خطوط من رمز الثعبان from buildbot.plugins import *
من خلال كتابة هذه السطور ، نحصل على التجميع التلقائي عند الضغط على المستودع ، ووجه الويب الجميل ، وإشعارات البريد الإلكتروني وغيرها من السمات لأي ci تحترم نفسها. يجب أن يكون معظم هذا الأمر واضحًا: إعدادات المجدول ، المجمعات ، والكائنات الأخرى مصنوعة على غرار الأمثلة المذكورة مسبقًا ، تكون قيمة معظم المعلمات بديهية. بالتفصيل ، سأركز فقط على إنشاء مصنع ، وعدت به سابقًا.
يتكون المصنع من
خطوات الإنشاء التي يجب على Buildbot إكمالها للمشروع. كما هو الحال مع الفئات الأخرى ، هناك العديد من الحلول الجاهزة. يتكون المصنع من خمس خطوات. كقاعدة عامة ، تتمثل الخطوة الأولى في الحصول على الحالة الحالية للمستودع ، وهنا لن نقوم بإجراء استثناء. للقيام بذلك ، نستخدم فئة
Git القياسية:
الخطوة الأولى factory = util.BuildFactory() factory.addStep(steps.Git( repourl = util.Property('repository'), workdir = 'sources', haltOnFailure = True, submodules = True, progress = True) )
بعد ذلك ، نحتاج إلى إنشاء دليل يتم فيه تجميع المشروع - سنقوم بإكمال بناء المصدر. قبل ذلك ، يجب أن تتذكر حذف الدليل إذا كان موجودًا بالفعل. وبالتالي ، نحن بحاجة إلى تنفيذ أمرين. سوف
تساعدنا فئة
ShellSequence في هذا:
الخطوة الثانية 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']) ]) )
الآن تحتاج إلى بدء CMake. للقيام بذلك ، من المنطقي استخدام أحد الفئتين -
ShellCommand أو
CMake . سوف نستخدم الأخير ، لكن الاختلافات ضئيلة: إنها عبارة عن
غلاف بسيط على الدرجة الأولى ، مما يجعله أكثر ملاءمة لتمرير وسيطات خاصة بـ CMake.
الخطوة الثالثة factory.addStep(steps.CMake( workdir = 'build', path = '../sources', haltOnFailure = True) )
الوقت لتجميع المشروع. كما في الحالة السابقة ، يمكنك استخدام
ShellCommand . وبالمثل ، هناك فئة
Compile ، وهي عبارة عن غلاف على
ShellCommand . ومع ذلك ، يعد هذا برنامجًا أكثر تعقيدًا: تقوم الفئة
Compile بمراقبة التحذيرات أثناء
التحويل البرمجي وتعرضها بدقة في سجل منفصل. هذا هو السبب في أننا سوف نستخدم فئة
ترجمة :
الخطوة الرابعة factory.addStep(steps.Compile( name = 'build project', workdir = 'build', haltOnFailure = True, warnOnWarnings = True, command = ['make']) )
أخيرًا ، قم بإجراء اختباراتنا. هنا
سنستخدم فئة
ShellCommand المذكورة سابقًا:
الخطوة الخامسة factory.addStep(steps.ShellCommand( name = 'run tests', workdir = 'build', haltOnFailure = True, command = ['ctest']) )
المثال الثاني: خط أنابيب كرمز
سأعرض هنا كيفية تنفيذ خيار ميزانية لتخزين اختبار المنطق مع رمز مصدر المشروع ، وليس في ملف تكوين ci-server. للقيام بذلك ، ضع ملف
.buildbot في المخزون مع الكود ، والذي يتكون كل سطر من كلمات ، يتم تفسير أولها كدليل للأمر الذي سيتم تنفيذه ، والباقي كأمر مع وسيطاته. بالنسبة لمشروع الحيوانات الأليفة الخاص بنا ،
سيبدو ملف
.buildbot كما يلي:
.Buildbot ملف مع الأوامر. rm -rf build
. mkdir build
build cmake ../sources
build make
build ctest
الآن نحن بحاجة إلى تعديل ملف التكوين Buildbot. لتحليل ملف
.buildbot ، سيتعين علينا كتابة فئة من خطوتنا الخاصة. ستقوم هذه الخطوة بقراءة ملف
.buildbot ، وبعد ذلك يضاف إلى كل سطر الخطوة
ShellCommand بالوسائط اللازمة. لإضافة خطوات بشكل حيوي ، سوف نستخدم أسلوب
build.addStepsAfterCurrentStep () . لا يبدو مخيفًا على الإطلاق:
فئة AnalyseStep 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)
بفضل هذا النهج ، أصبح مصنع المجمع أكثر بساطة وأكثر تنوعا:
مصنع لتحليل .buildbot الملف 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) )
المثال الثالث: العامل كرمز
الآن تخيل أنه بجانب رمز المشروع ، نحتاج إلى تحديد تسلسل الأوامر ، ولكن بيئة التجميع. في الواقع ، نحدد العامل. قد يبدو ملف
.buildbot كما يلي:
ملف بيئة البناء{
"workers": ["stretch32", "wheezy32"]
}
سيصبح ملف تكوين Buildbot في هذه الحالة أكثر تعقيدًا ، لأننا نريد ربط التجميعات في بيئات مختلفة (في حالة فشل بيئة واحدة على الأقل ، يعتبر الالتزام بالكامل غير صالح للعمل). مستويين مساعدتنا في حل المشكلة. سيكون لدينا عامل محلي يقوم بتوزيع ملف
.buildbot ويقوم بتشغيل
الإنشاءات على العمال المطلوبين. أولاً ، كما في المثال السابق ،
سنكتب خطوتنا لتحليل ملف
.buildbot . لبدء التجميع على عامل محدد ، يتم استخدام حزمة من خطوة
الزناد ونوع خاص من
برامج الجدولة
TriggerableScheduler . أصبحت خطوتنا أكثر تعقيدًا بعض الشيء ، ولكنها مفهومة تمامًا:
فئة AnalyseStep 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([])
سوف نستخدم هذه الخطوة على العامل المحلي. يرجى ملاحظة أننا وضعنا العلامة على جامعنا "Pet Project Builder". مع ذلك ، يمكننا تصفية
MailNotifier ، ونقول لها أنه ينبغي إرسال الرسائل فقط إلى جامعي معين. إذا لم تتم عملية التصفية هذه ، فعند بناء الالتزام على بيئتين ، سنتلقى ثلاثة أحرف.
جامع عام 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 )]
يبقى لنا أن نضيف الجامعين ونفس المجدات القابلة للتشغيل لجميع العاملين لدينا الحقيقيين:
جامعي في البيئة المناسبة 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) )
(بناء صفحة من مشروعنا في بيئتين)المثال الرابع: حرف واحد لكل عدة عمليات
إذا كنت تستخدم أيًا من الأمثلة المذكورة أعلاه ، يمكنك ملاحظة ميزة واحدة غير سارة. نظرًا لإنشاء حرف واحد لكل التزام ، عندما ندفع الفرع بـ 20 عملية جديدة ، سنتلقى 20 حرفًا. تجنب ذلك ، كما في المثال السابق ، سنساعد مستويين. نحتاج أيضًا إلى تعديل الفصل للحصول على التغييرات. بدلاً من إنشاء العديد من كائنات التغيير ، سننشئ كائنًا واحدًا فقط ، حيث يتم إرسال قائمة بجميع عمليات الالتزام. على عجل ، يمكن القيام بذلك مثل هذا:
فئة 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 } }
للعمل مع كائن التغيير غير المعتاد هذا ، نحتاج إلى خطوة خاصة بنا ، والتي تنشئ ديناميكيًا خطوات تجمع التزامًا محددًا:
فئة 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
أضف جامعنا المشترك ، الذي يشارك فقط في تشغيل التجميعات للالتزامات الفردية. يجب وضع علامة عليه حتى يتم تصفية إرسال الرسائل بهذه العلامة نفسها.
جلب البريد العام 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 )]
يبقى لإضافة فقط جامع لارتكاب الفردية. نحن لا نضع علامة على هذا المجمع بعلامة ، وبالتالي لن يتم إنشاء حروف له.
جلب البريد العام 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) )
الكلمات النهائية
لا تحل هذه المقالة بأي حال من الأحوال محل قراءة الوثائق الرسمية ، لذلك إذا كنت مهتمًا بـ Buildbot ، فيجب أن تكون خطوتك التالية هي قراءتها. تتوفر الإصدارات الكاملة من ملفات التكوين لجميع الأمثلة على
جيثب . روابط ذات صلة ، تم الحصول عليها من معظم المواد الخاصة بالمقال:
- الوثائق الرسمية
- رمز مصدر المشروع