(ืชืืื ื ืืืช
ืืื!
ืฉืื ืืืื ื ืฆ'ืจืงืื, ืื ื ืืชืื ืช ืืฆืืืช ืคืืชืื ืืืืจืช ืืจืืื ืคืืืืืื.
ืืืฉืจ ืืชืืืืื ืื ืคืจืืืงื ืืืื, ืืชื ืืชืืื ืืืฉืื: "ืืืืื ืชืืื ื ืืื ืืืื ืืืฉืชืืฉ ืืื ืืืคื ืื?" ืคืจืืืงื IT ืขืืืจ ืืกืคืจ ืฉืืืื ืืคื ื ืฉืืจืืจ ืืืจืกื ืืืื. ืื ืืื ืืืฉืจ ืฉืจืฉืจืช ืืฉืืืื ืืืื ืืื ืืืืืืืืช. ืืชืืืื ืืืืืืืื ืฉื ืฉืืจืืจ ืืจืกื ืืืฉื ืฉื ืคืจืืืงื IT ืขืฆืื ื ืงืจื ืืื ืืืจืฆืื ืืชืืฉืืช. BuildBot ืืชืืื ืืขืืืจ ืืื ืขืืืจื ื ืืืืฉืื ืชืืืื ืื.
ืืืืืจ ืื ืืืืืชื ืืกืคืง ืกืงืืจื ืืืืืช ืฉื ืืืคืฉืจืืืืช BuildBot. ืืื ืืชืืื ื ืืื ืืกืืืืช? ืืื ืืคื ืืช ืืืื ืืืื ืืื ืืช ืืืชื ืืขืจืืช ืืืกื ืขืืืื ืืคืงืืืืืช ืจืืืื? ืืชื ืืืื ืืืืฉื ืืช ืื ืืกืืื ืฉืื ื ืืขืฆืื ืขื ืืื ืืฆืืจืช ืฉืืจืืช ืขืืื ืืื ืืื ืืืืืงื ืฉื ืืคืจืืืงื ืฉืื ืืืืื ื ืฉืื.
ืชืืื
ืชืืื
1. ืืื BuildBot?
ืืขืืจ ื-habr-e ื ืชืงืืชื ืืืืืจืื ืขื ืืืฉืื ืืื ืืืจืฆืื ืืชืืฉืืช ืขื BuildBot. ืึฐืึธืฉืึธื,
ืชืคืกืืง! ืืื ืืืฉืื ืืืืช ืืฉืชืืฉ ืืื ืืคืจืืืงืืื ืฉืื? ืืกืชืืจ ืฉืื
ืื ืื ืืืืืืื ืฉื ืื ืฉืื ืืฉืชืืฉืื Buildbot? ืืืจื ืืื, ืืฉ ืืืื ืืืจืื: ืืงืจืช ืงืจืื ะธ ื'ื ืงืื ืก. ืื ื ืืขื ื ืื. ืืจืื ืืืฉืืืืช ื'ื ืงืื ืก ืืืืืช ืชืกืคืืง. ืืชืืจื, BuildBot - ืืกืชืื ืืืชืจ, ืืขืื ืฉืืขืืืช ื ืคืชืจืืช ืฉื ืืฆืืจื ืคืฉืืื ืืื ื ื'ื ืงืื ืก. ืืืืืจื ืืืืืื ืฉืื. ืื ืืืืืื ืฉืื ื ืืืคืฉืื ืืื ืืคืจืืืงื ืืขื ืืชืคืชื, ืืืืข ืื ืืืืืจ ืืื ืฉืืืคืฉืจ, ืืื ืืฉืืืื ืคืฉืืืื, ืืืฉืื ืืขืจืืช ืื ืืื ืืขืืช ืืื ืืจืืงืืืืืืช ืืืืฉืง ืืืืืื.
ืืื ืฉืคืจืืืงื ืืืขื ืฉืื ืืชืื ืืคืืชืื, ื ืฉืืืช ืืฉืืื: "ืืื ืื ืืืืืจ ืืขืจืืช ืืื ืืืจืฆืื ืืขืืช ืืืฉืง ืืจืืจ ืืืืื ืช ืืฉืคื ืฉืื ื ืขืฉื ืฉืืืืฉ ืืคืจืืืงื?" ืืขืืฉืื ืืืืข ืืืื ืืืฆืื ืืช ืืืชืจืื ืืช BuildBot.
ืื, "ืืจืืืขืืื ืืืื ืกืืจืืื ืืืืช" ืฉืื ื. ืืขืฆืื, ืืืืืชื ืืจืืข ืชืืื ืืช BuildBot:
- ืืืื ืืกืืจืช ืงืื ืคืชืื ืชืืช ืจืืฉืืื GPL
- ืืื ืืฉืืืืฉ ื-python ืืืื ืชืฆืืจื ืืชืืืืจ ืืคืขืืืืช ืื ืืจืฉืืช
- ืื ืืืืื ืืช ืืงืื ืืขื ื ืืืืืื ื ืขืืื ืืชืืฆืขืช ืืืจืืื
- ืืื, ืืืกืืฃ, ืืืจืืฉืืช ืืืื ืืืืืืช ืืืืจื. ืืคืจืืกื ืืืจืฉืช ืคืืชืื ืืืคืืชื, ืืืื ื ืืืจืฉืช ืืืื ื ืืืจืืืืืืช ืืืืื ืช ื'ืืืื.
2. ืงืื ืกืคื ืืืืืืช BuildMaster
ืืจืืื ืืืจืืืืงืืืจืช ืืืืงืช ืืืฉืืืืช ืืื BuildMaster. ืื ืฉืืจืืช ืฉ:
- ืขืืงื ืืืจ ืฉืื ืืืื ืืขืฅ ืืืงืืจ ืฉื ืืคืจืืืงื
- ืฉืืื ืคืงืืืืช ืฉืฆืจืื ืืืฆืข ืขื ืืื ืฉืืจืืช Worker ืืื ืืื ืืช ืืช ืืคืจืืืงื ืืืืืืง ืืืชื
- ืืืืืข ืืฉืชืืฉืื ืืืื ืชืืฆืืืช ืืคืขืืืืช ืฉื ื ืงืื
BuildMaster ืืืืืจ ืืืืฆืขืืช ืงืืืฅ master.cfg. ืืงืืืฅ ืืื ื ืืฆื ืืฉืืจืฉ BuildMaster. ืืืืฉื ืืจืื ืืืฆื ื ืืฆืจ ืืฉืืจืฉ ืืื. ืืงืืืฅ ืขืฆืื master.cfg ืืืื ืกืงืจืืคื python ืฉืืฉืชืืฉ ืืงืจืืืืช BuildBot.
ืืืืืืืงื ืืืฉืื ืืื ืืืืชืจ BuildBot ืืฉ ืฉื ืขืืื. ืฉืืจืืช ืื ืืืื ืืืืืช ืืืคืขื ืขื ืืืจื ืืืจ ืขื ืืขืจืืช ืืคืขืื ืืืจืช, ืื ืืืื ืขื ืื ืฉืื BuildMaster. ืื ืืืื ืืืชืงืืื ืื ืืกืืืื ืืืจืืืืืืช ืฉืืืื ื ืืืืืื ืขื ืืืืืืช ืืืฉืชื ืื ืืฉืื. ื ืืชื ืืืืื ืืช ืืกืืืืืช ืืืืืจืืืืืืืช ืืืื ืืืืฆืขืืช ืืื ืขืืจ ืคืืชืื ืืื virtualenv, venv.
BuildMaster ืืฉืืจ ืคืงืืืืช ืืืืื ืขืืื-y, ืืืื, ืืชืืจื, ืืืื ืืืชื. ืืืืืจ, ืืกืชืืจ ืฉืชืืืื ืืื ืืื ืืืืืืงื ืฉื ืคืจืืืงื ืืืื ืืืืืฉื ืขืืื-e ืขื Windows ืื-Worker ืืืจ ืขื ืืื ืืงืก.
ืืืืืง ืงืืื ืืงืืจ ืฉื ืืคืจืืืงื ืืชืจืืฉืื ืืื ืืื ืืื ืขืืื-ื.
3. ืืชืงื ื
ืื ืืื ื ืื. ืื ื ืืฉืชืืฉ ืืืืืื ืื 18.04 ืืืืจื. ืื ื ืืฉืื ืืื ืขื ืื BuildMaster-ื ืืืื ืขืืื-ื. ืืื ืชืืืื ืขืืื ืืืชืงืื python3.7:
sudo apt-get update
sudo apt-get install python3.7
ืืื ืฉืฆืจืื python3.7.2 ืืืงืื 3.7.1, ืืชื ืืืื ืืขืฉืืช ืืช ืืคืขืืืืช ืืืืืช:
sudo apt-get update
sudo apt-get software-properties-common
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt-get install python3.7
sudo ln -fs /usr/bin/python3.7 /usr/bin/python3
pip3 install --upgrade pip
ืืฉืื ืืื ืืื ืืชืงื ื ืฆืืืฅ ะธ BuildBot, ืืื ืื ืืืืืืช ืืืืคืฉืจืืช ืื ืืืฉืชืืฉ ืืคืื ืงืฆืืื ืืืืช ื ืืกืคืช BuildBot-ื.
/*ะัะต ััะพ ะฟะพะด sudo ะฑัะดะตั ัััะฐะฝะพะฒะปะตะฝะฝะพ ะดะปั ะฒัะตั
ะฟะพะปัะทะพะฒะฐัะตะปะตะน ะฒ ะดะธัะตะบัะพัะธั /usr/local/lib/python3.7/dist-packages*/
#ะะฐ ั
ะพััะต ะบะพัะพััะน ะฟัะพะธะทะฒะพะดะธั ะผะพะฝะธัะพัะธะฝะณ Worker-ะพะฒ
sudo pip install twisted #ะะธะฑะปะธะพัะตะบะฐ twisted
sudo pip install buildbot #BuildMaster
#ะะพะฟะพะปะฝะธัะตะปัะฝัะน ััะฝะบัะธะพะฝะฐะป
pip install pysqlite3 #ะฃััะฐะฝะฐะฒะปะธะฒะฐะตะผ ะฑะฐะทั sqllite ะฒ ััะตะฑะฝัั
ัะตะปัั
pip install jinja2 #framework ะฝะฐะฟะพะดะพะฑะธะต django, ะดะปั web ะธ ะดะปั ะฟะพััะพะฒัั
ัะฐัััะปะปะพะบ
pip install autobahn #Web cะพะบะตัั ะดะปั ัะฒัะทะธ BuildMaster->Worker
pip install sqlalchemy sqlalchemy-migrate #ะะปั ะพัะพะฑัะฐะถะตะฝะธั ัั
ะตะผั ะฑะฐะทั ะดะฐะฝะฝัั
#ะะปั Web ะพัะพะฑัะฐะถะตะฝะธั BuildBot-a
pip install buildbot-www buildbot-grid-view buildbot-console-view buildbot-waterfall-view
pip install python-dateutil #ะัะพะฑัะฐะถะตะฝะธะต ะดะฐั ะฒ web
#ะะฐ ััะพัะพะฝะต ั
ะพััะฐ ะบะพัะพััะน ะฝะตะฟะพััะตะดััะฒะตะฝะฝะพ ะพัััะตััะฒะปัะตั ัะฑะพัะบั ะธ ัะตััะธัะพะฒะฐะฝะธะต
pip install buildbot-worker #Worker
#ะะพะฟะพะปะฝะธัะตะปัะฝัะน ััะฝะบัะธะพะฝะฐะป
sudo pip install virtualenv #ะะธัััะฐะปัะฝะฐั ััะตะดะฐ
4. ืฆืขืืื ืจืืฉืื ืื
ืืื ืืืฆืืจ BuildMaster. ืื ืืืื ืืชืืงืืื ืฉืื ื /home/habr/master.
mkdir master
buildbot create-master master # ะกะพะฑััะฒะตะฝะฝะพ ัะดะตัั ะธ ัะพะทะดะฐะตะผ
mkdir worker
buildbot-worker create-worker --umask=0o22 --keepalive=60 worker localhost:4000 yourWorkerName password
ืืฉืืชื ืจืฅ ืขืืื, ืื ืืืจืืจืช ืืืื ืืื ืชืืฆืืจ ื /ืืืช/ืืืจ/ืขืืื ืชืืงืืื ืขื ืฉื ืืคืจืืืงื, ืืืฆืืื ื master.cfg. ืืืชืืงืืื ืขื ืฉื ืืคืจืืืงื ืืื ืชืืฆืืจ ืกืคืจืืื ืึดืื ืึนืช, ืืืืฉืื ืืขืฉืืช ืืืช ืืชืฉืืื. ืกืคืจืืืช ืขืืืื ืขืืืจ ืขืืื-ืืืื ืชืืคืื ืืกืคืจืืื /home/habr/yourProject/build.
"ืืคืชื ืืื
ืืขืืฉืื ืืฉืืื ืื ืืชืืชื ืืช ืืคืกืงื ืืงืืืืช: ืชืกืจืื ืฉ ืื ืืืจืืฉ ื ืขืืื-ืืืืฆืข ืืจืืืง ืืกืคืจืืื ืื ืื ืืืืฆืข ืืืืืื ืฉืืกืงืจืืคื ืืื ืืจืฉืืืช ืืืคืขืื. ืืื ืืชืงื ืืช ืืืฆื, ืชืืืงืง ืืืคืชื
BuildMaster ะธ ืขืืื ืืืฆืืจ ืงืฉืจ ืืื ืขื ืืฉื ื. ืงืืจื ืฉืื ืืชื ืชืง ื ืขืืื ืืืื ืืื ืื ืืชืืืื ื BuildMaster-ื. ืื ืืื ืชืืืื, ืืืืืืจ ืืืคืขื ืืืืฉ. ืึทืคึฐืชึตืึท --keepalive=60 ืจืง ืฆืจืื ืืฆืืื ืืช ืืืื ืฉืืืจืื ืึฐืึทืึผึตืจ ืืืชืื.
5. ืชืฆืืจื. ืืชืืื ืฉืื ืืืจ ืฉืื
ืชึฐืฆืึผืจึธื BuildMaster ืืชืืฆืข ืืฆื ืืืืื ื ืฉืื ืืืฆืขื ื ืืช ืืคืงืืื ืืืฆืืจ-ืืืกืืจ. ืืืงืจื ืฉืื ื, ืื ืกืคืจืืื /home/habr/master. ืงืืืฅ ืชืฆืืจื master.cfg ืขืืืื ืื ืงืืื, ืืื ืืคืงืืื ืขืฆืื ืืืจ ืืฆืจื ืืช ืืงืืืฅ master.cmg.sample. ืืชื ืฆืจืื ืืฉื ืืช ืืช ืืฉื ื master.cfg.sample ะฒ master.cfg
mv master.cfg.sample master.cfg
ืืืื ื ืคืชื ืืช ืื master.cfg. ืืืืื ื ืกืชืื ืืื ืื ืืืจืื. ืืืืจื ืื, ืืืื ื ื ืกื ืืืฆืืจ ืงืืืฅ ืชืฆืืจื ืืฉืื ื.
master.cfg
c['change_source'] = []
c['change_source'].append(changes.GitPoller(
'git://github.com/buildbot/hello-world.git',
workdir='gitpoller-workdir', branch='master',
pollInterval=300))
c['schedulers'] = []
c['schedulers'].append(schedulers.SingleBranchScheduler(
name="all",
change_filter=util.ChangeFilter(branch='master'),
treeStableTimer=None,
builderNames=["runtests"]))
c['schedulers'].append(schedulers.ForceScheduler(
name="force",
builderNames=["runtests"]))
factory = util.BuildFactory()
factory.addStep(steps.Git(repourl='git://github.com/buildbot/hello-world.git', mode='incremental'))
factory.addStep(steps.ShellCommand(command=["trial", "hello"],
env={"PYTHONPATH": "."}))
c['builders'] = []
c['builders'].append(
util.BuilderConfig(name="runtests",
workernames=["example-worker"],
factory=factory))
c['services'] = []
c['title'] = "Hello World CI"
c['titleURL'] = "https://buildbot.github.io/hello-world/"
c['buildbotURL'] = "http://localhost:8010/"
c['www'] = dict(port=8010,
plugins=dict(waterfall_view={}, console_view={}, grid_view={}))
c['db'] = {
'db_url' : "sqlite:///state.sqlite",
}
5.1 BuildmasterConfig
c = BuildmasterConfig = {}
BuildmasterConfig - ืืืืื ืืกืืกื ืฉื ืงืืืฅ ืืชืฆืืจื. ืื ืืืื ืืืืืื ืืงืืืฅ ืืชืฆืืจื. ืื ืืืืช ืืฉืืืืฉ, ืืื ืื ืืืฆื ืืงืื ืืชืฆืืจื "ื". ืืืชืจืืช
ืขืืื 5.2
c['workers'] = [worker.Worker("example-worker", "pass")]
ืืคืขื ืื ื ืืฆืืื ืื BuildMaster-y ืจืฉืืื ืฉื ืขืืื-ืก. ืขืฆืื ืขืืื ืืฆืจื ื
5.3 change_source
c['change_source'] = []
c['change_source'].append(changes.GitPoller(
'git://github.com/buildbot/hello-world.git',
workdir='gitpoller-workdir', branch='master',
pollInterval=300))
ืืคื ืืคืชื change_source dictionary c ื ืงืื ืืืฉื ืืจืฉืืื ืฉืื ื ืจืฆื ืืฉืื ืืืืืืงื ืฉืืกืงืจ ืืช ืืืืืจ ืขื ืงืื ืืืงืืจ ืฉื ืืคืจืืืงื. ืืืืืื ืืฉืชืืฉืช ืืืืืจ Git ืฉื ืฉืื ืืืจืืืืื ืืกืืืืื.
ืืืืขืื ืืจืืฉืื ืืื ืื ืชืื ืืืืืจ ืฉืื.
workdir ืืืืฆื ืืช ืื ืชืื ืืชืืงืื ืฉืื ื ืืฆื ืืฆื ืขืืื-ืงืจืื ืื ืชืื /home/habr/worker/yourProject/build git ืืืืกื ืืช ืืืจืกื ืืืงืืืืช ืฉื ืืืืืจ.
ืกื ืืฃ ืืืื ืขื ืฃ ืกืคืฆืืคื ืืืืืจ ืฉืืฉ ืืขืงืื ืืืจืื.
pollInterval ืืืื ืืช ืืกืคืจ ืืฉื ืืืช ืฉืืืจืื BuildMaster ืืกืงืจ ืืช ืืืืืจ ืืฉืื ืืืื.
ืืฉื ื ืืกืคืจ ืฉืืืืช ืืขืงืื ืืืจ ืฉืื ืืืื ืืืืืจ ืฉื ืคืจืืืงื.
ืืฉืืื ืืคืฉืืื ืืืืชืจ ืืื ืืกืงืจืื, ืื ืฉืืจืื ืขื ืื BuildMaster ืืขืช ืืขืช ืกืืงืจ ืืช ืืฉืจืช ืขื ืืืืืจ. ืื ืืืฆืข ืฉืืงืฃ ืืช ืืฉืื ืืืื ืืืืืจ, ืื ืื BuildMaster ืชืืฆืืจ ืืืืืืงื ืคื ืืื ืขื ืขืืืื ืืกืืื ืฉืื ืื ืืืฉืืื ืืืชื ืืืืคื ืืืืจืืขืื ืืชืืื, ืฉืชืฉืืง ืืช ืืฉืืืื ืืื ืืืช ืืคืจืืืงื ืืืืืืงืชื ืขืืื-ื. ืืื ืืฉืืืื ืืืื ืืฆืืื ื ืขืืืื ืืืืจ. ืืืืืง ืขื ืขืืืืคืขืืื ืื ืชืืฆืืจ ืขืืชืง ืืงืืื ืฉื ืืืืืจ. ืืคืจืืื ืฉื ืชืืืื ืื ืืืืกื ืืืื ืืฉื ื ืืกืขืืคืื ืืืืื. (
ืฉืืื ืืืื ืืืช ืขืื ืืืชืจ ืืืขืงื ืืืจ ืฉืื ืืืื ืืืืืจ ืืื ืืฉืืื ืืืืขืืช ืืฉืืจืืช ืืืฉืจืช ืืืืจื ืืืชื BuildMaster- ืขื ืฉืื ืื ืงืืื ืืืงืืจ ืฉื ืืคืจืืืงื. ืืืงืจื ืื, ืืจืืข ืฉืืืคืชื ืขืืฉื ืืืฆืข, ืืฉืจืช ืขื ืืืืจ ืืคืจืืืงื ืืฉืื ืืืืขื BuildMaster-y. ืืืื, ืืชืืจื, ืืืจื ืืืชื ืขื ืืื ืืฆืืจืช ืืืืืืงื PBChangeSource. ืืืืจ ืืื, ืืืืืืงื ืื ืืืขืืจ ืื ืืชืืื, ืืฉืจ ืืคืขืื ืืช ืืฉืืืื ืืื ืืืช ืืคืจืืืงื ืืืืืงืชื. ืืืง ืืฉืื ืืฉืืื ืื ืืื ืืขืืืื ืขื ืื-ืกืงืจืืคืืื ืฉื ืฉืจืช ืืืืืจ. ืืชืกืจืื ืื-ื, ืืืจืื ืืขืืืื ืคืขืืืืช ืืืฉืจ ืืืฆืข-ื, ืืชื ืฆืจืื ืืืชืงืฉืจ ืืฉืืจืืช ืฉืื ืื ืฉืื ืืฆืืื ืืช ืืชืืืช ืืจืฉืช BuildMaster-ื. ืืชื ืื ืฆืจืื ืืฆืืื ืืช ืืฆืืืช ืืจืฉืช ืฉืชืืืื PBChangeSource. PBChangeSource, ืืื, ืืื ืืืง BuildMaster-ื. ืฉืืื ืื ืชืืจืืฉ ืืืฉืืจ ืื ืื-a ืืฉืจืช ืฉืื ื ืืฆื ืืืืจ ืืคืจืืืงืืื. ืชืืืื ืืืื ืขืืื ืืืฆืข ืืืืื ืฉื ืืืืืจ.
5.4 ืืชืืื ืื
c['schedulers'] = []
c['schedulers'].append(schedulers.SingleBranchScheduler(
name="all",
change_filter=util.ChangeFilter(branch='master'),
treeStableTimer=None,
builderNames=["runtests"]))
c['schedulers'].append(schedulers.ForceScheduler(
name="force",
builderNames=["runtests"]))
ืืชืืื ืื โ ืืื ืืืื ื ืฉืคืืขื ืืืจืืืจ ืฉืืชืืื ืืช ืื ืฉืจืฉืจืช ืืืจืืื ืืืืืืงืืช ืฉื ืืคืจืืืงื.
ืืืชื ืฉืื ืืืื ืฉื ืจืฉืื change_source, ืืฉืชื ื ืืชืืืื ืืขืืืื BuildBot-ื ืืืชื ืื ืฉืื ืื ืืขืืฉืื ืื Sheduler ืืืชืืกืก ืขืืืื, ืืื ืืื ื ืืงืฉืืช ืืชืืืืช ืชืืืื ืื ืืืช ืืคืจืืืงื. ืขื ืืืช, ืืื ืื ืงืืืข ืืชื ืืืงืฉืืช ืืืื ืืืขืืจืืช ืืืืฉื ืืชืืจ. ืืคืฅ
ืื ื ืจืืืื ืฉืืืืืื ืื ืืืืืช ืืื ืืชืืื ืื ื ืืฆืจื 2 ืืืงืื. ืืชืจ ืขื ืื, ืืื ืืื ืืฉ ืกืื ืืฉืื.
SingleBranchScheduler - ืืื ืืฉืืขืืจืื ืืคืืคืืืจืืื ืืืืชืจ ืืืื ืืืื ืื. ืืื ืฆืืคื ืืขื ืฃ ืืื ืืืืคืขื ืขื ืืื ืฉืื ืื ืืชืืขื ืื. ืืืฉืจ ืืื ืจืืื ืฉืื ืืืื, ืืื ืืืื ืืขืื ืืช ืฉืืืืช ืืงืฉืช ืืื ืืื (ืืื ืืชืงืืคื ืืืฆืืื ืช ืืคืจืืืจ ืืืืืื treeStableTimer). ื ืฉื ืืืืืจ ืืช ืฉื ืืื ืืืื ืื ืฉืืืฆื ืื BuildBot-ืืืฉืง ืืื ืืจื ื. IN ืฉืื ืื ืืกื ื ื ืงืืข ืืกื ื, ืืืืจ ืืขืืจ ืืืื ืฉืื ืืืื ืืกื ืืฃ ืืืจืืื ืืืื ืืืื ืื ืืฉืืื ืืงืฉื ืืื ืืื. IN ืฉืืืช ืืื ื ืืฉื ืืฆืืื ืืื ื-ื, ืฉื ืงืืข ืืขื ืืืืืจ ืืืชืจ. ืืฉื ืืืงืจื ืฉืื ื ืืืื ืืื ืืฉื ืืคืจืืืงื: ืืืืื ืฉืื.
ForceScheduler ืืืจ ืืืื ืคืฉืื. ืกืื ืื ืฉื ืืื ืืื ืื ืืืคืขื ืขื ืืื ืืืืฆืช ืขืืืจ BuildBot-ืืืฉืง ืืื ืืจื ื. ืืคืจืืืจืื ืืฉ ืืืชื ืืืืช ืืื ื SingleBranchScheduler.
ื .ื ืืก' 3. ืืืื ืื ืืืขืื
ืชึฐืงืึผืคึธืชึดื ืืื ืืื ืืื ืื ืฉืคืืขื ืืชืืืจืืช ืืกืืืืช ืงืืืขื ืืืื. ืืฉืืื ื ืจืืืช ืืขืจื ืื
from buildbot.plugins import schedulers
nightly = schedulers.Periodic(name="daily",
builderNames=["full-solaris"],
periodicBuildTimer=24*60*60)
c['schedulers'] = [nightly]
5.5 BuildFactory
factory = util.BuildFactory()
factory.addStep(steps.Git(repourl='git://github.com/buildbot/hello-world.git', mode='incremental'))
factory.addStep(steps.ShellCommand(command=["trial", "hello"],
env={"PYTHONPATH": "."}))
periodicBuildTimer ืืฆืืื ืืช ืืืื ืฉื ืืืืืจืืืช ืื ืืฉื ืืืช.
BuildFactory ืืืฆืจ ืกืคืฆืืคื ืึดืื ืึนืช, ืื ืื ืืื ื ืฉืืื ื ืขืืื. ืืชืื BuildFactory ืืฆืืื ืืช ืืฉืืืื ืฉืืฉ ืืืฆืข ืขืืื-y. ืฉืืืื ืืชืืืกืคืื ืขื ืืื ืงืจืืื ืืฉืืื addStep
ืืฉืื ืืจืืฉืื ืฉื ืืกืฃ ืืืืืื ืื ืืื git clean -d -f -f -xืื ืงืืคืช git. ืคืขืืืืช ืืื ืืืืืืช ืืคืจืืืจ ืฉืืื, ืฉืืื ื ืืืฆืืจ ืืืืจืืจ ืื ืืจืื ืขื ืขืจื ืืจืืจืช ืืืื ืืจื... ืคึผึธืจึธืึถืึถืจ mode='incremental' ืืฆืืื ืฉืืงืืฆืื ืื ืืืกืคืจืืื ืฉืื ืฆ'ืฆ'ืืื, ืืขืืื ืืกืจ ืืืืืจ, ื ืฉืืจ ืืื ื ืืืขื.
ืืฉืื ืืฉื ื ืฉื ืืกืฃ ืืื ืงืจืืื ืืกืงืจืืคื ืึดืฉืืคึผึธื ืขื ืคืจืืืจ ืฉืืื ืืฆื ืขืืื-a ืืืกืคืจืืื /home/habr/worker/yourProject/build ืขื ืืฉืชื ื ืืกืืืื PATHONPATH=... ืืคืืื, ืืชื ืืืื ืืืชืื ืกืงืจืืคืืื ืืฉืื ืืืืคืขืื ืืืชื ืืฆื ืขืืื-ืืื ืฆืขื util.ShellCommand. ื ืืชื ืืืงื ืกืงืจืืคืืื ืืื ืืฉืืจืืช ืืืืืจ. ืืื ื ืฆ'ืฆ'ืืื-ื ืื ืืคืื /home/habr/worker/yourProject/build. ืขื ืืืช, ืื ืืฉ ืฉื ื "ืืื":
- ืขืืื ืืฉ ืืืฆืืจ ืขื ืืคืชื
--umak ืื ืฉืื ืื ืืืกืื ืืืืืืช ืืืฆืืข ืืืืจ ืืชืฉืืื-ื. - ื ืืืืคืช ืืื-ื ืืืกืงืจืืคืืื ืืืื ืืชื ืฆืจืื ืืื ืืฆืืื ืืช ืืืืคืืื ื ืืชื ืืืืฆืืขืื ืฉืืืืฉื ืฆ'ืฆ'ืืื-e ืื ืืืื ืืช ืืืืืช ืืืฆืข ืืช ืืกืงืจืืคื Git.
5.6 ืืื ืื
c['builders'] = []
c['builders'].append(util.BuilderConfig(name="runtests",
workernames=["example-worker"],
factory=factory))
ืขื ืื ืฉืืฉ ืืื ื ื ืืืจ
c['builders'] = []
c['builders'].append(util.BuilderConfig(name="yourProject",
workernames=["yourWorkerName"],
factory=factory))
ืขืืฉืื ืื ื ืืกืคืจ ืื ืขื ืืคืจืืืจืื BuilderConfig.
ืฉื ืืฆืืื ืืช ืืฉื ืืื ื-ื. ืืื ืงืจืื ื ืืื ืืืืื ืฉืื... ืื ืืืืจ ืฉืืืืฉื ืขืืื- ื ืชืื ืื ืืืืืฆืจ /home/habr/worker/yourProject/build. Sheduler ืืืคืฉ ืืื ื ืจืง ืืฉื ืืื.
ืฉืืืช ืขืืืืื ืืืื ืืืืืื ืขืืื-ืก. ืืื ืืื ืืื ืืฉ ืืืืกืืฃ ื['ืขืืืืื'].
ืืคืขื - ืกืคืฆืืคื ืึดืื ืึนืช, ืืืื ืืื ืงืฉืืจ ืืื ื. ืืื ืืฉืื ืืช ืืืคืฅ ืึดืื ืึนืช ืขื ืขืืื ืืื ืืืฉืืื ืืช ืื ืืฉืืืื ืืืืืืื ืืื ืึดืื ืึนืช-ื.
6. ืืืืื ืืชืฆืืจื ืืฉืื
ืื ื ืืจืืืืงืืืจืช ืืคืจืืืงื ืืืืืื ืฉืื ื ืืฆืืข ืืืืฉื ืืืืฆืขืืชื BuildBot
.
ืื ื ื ืฉืชืืฉ ืืืขืจืืช ืืงืจืช ืืจืกืืืช svn. ืืืืืจ ืขืฆืื ืืืืงื ืืขื ื ืืืฉืื. ืื ื ืืืชืืืช ืฉื ืืขื ื ืืื
BuildMaster ะธ ืขืืื ืืจืืฅ ืขื ืืืชื ืืืจื project.host .BuildMaster ืืืืกื ืืช ืืงืืฆืื ืฉืื ืืชืืงืืื /home/habr/master. ืขืืื ืืื ืืืืืกื ืื ืชืื ืืื /ืืืช/ืืืจ/ืขืืื. ืชืงืฉืืจืช ืชืืืืืืช BuildMaster-ื ื ืขืืื-a ืืชืืฆืข ืืจื ืืฆืืื 4000 ืืคื ืืคืจืืืืงืื BuildBot-ื, ืืืืืจ 'ืคื' ื ืืื.
ืคืจืืืงื ืืืขื ืืชืื ืืืื ื- Python. ืืืฉืืื ืืื ืืขืงืื ืืืจ ืืฉืื ืืืื ืฉืื, ืืืฆืืจ ืงืืืฅ ืืคืขืื, ืืืฆืืจ ืชืืขืื ืืืขืจืื ืืืืงืืช. ืืืงืจื ืฉื ืืฉื, ืื ืืืคืชืืื ืฆืจืืืื ืืฉืืื ืืืืขื ืืืื"ื ืืืฆืืื ืช ืฉืืฉ ืคืขืืื ืื ืืืฆืืืช.
ืชืฆืืืช ืืื ืืจื ื BuildBot ื ืชืืืจ ืืืฆืืื 80 ืขืืืจ project.host. ืืื ืฆืืจื ืืืชืงืื ืืช Apatch. ืืืืง ืืืกืคืจืืื ืืคืืชื ืืฉ ืืืจ ืฉืจืช ืืื ืืจื ื, BuildBot ืืฉืชืืฉ ืื.
ืืืืกืื ืืืืข ืคื ืืื ืขืืืจ BuildBot ืื ืื ื ื ืฉืชืืฉ 12te.
ื ืืจืฉ ืืืจื ืขืืืจ ืืืืืจ smtp.your.domain - ืื ืืืคืฉืจ ืฉืืืืช ืืืชืืื ืืืืืืจ [ืืืื ืืืื"ื] ืืื ืืืืืช. ืื ืขื ืืืืจื 'SMTP ืืคืจืืืืงืื ื ืฉืืข ืืืืฆื 1025.
ืฉื ื ืื ืฉืื ืืขืืจืืื ืืชืืืื: ืื ืื ะธ ืืืฉืชืืฉ. ืื ืื ืื ืื BuildBot. ืืืฉืชืืฉ ืืื ืืืื ืฉืืชืืืื ืืืฆืข-ืก.
ืงืืืฅ ื ืืชื ืืืืฆืืข ื ืืฆืจ ืืืืฆืขืืช pyinstaller. ืืชืืขืื ื ืืฆืจ ืืืืฆืขืืช ืื ืืืฆื.
ืขืืืจ ืืืจืืืืงืืืจื ืืื ืืชืืชื ืืช ืื: master.cfg:
master.cfg
import os, re
from buildbot.plugins import steps, util, schedulers, worker, changes, reporters
c= BuildmasterConfig ={}
c['workers'] = [ worker.Worker('yourWorkerName', 'password') ]
c['protocols'] = {'pb': {'port': 4000}}
svn_poller = changes.SVNPoller(repourl="https://svn.host/svn/yourProject/trunk",
svnuser="user",
svnpasswd="password",
pollinterval=60,
split_file=util.svn.split_file_alwaystrunk
)
c['change_source'] = svn_poller
hourlyscheduler = schedulers.SingleBranchScheduler(
name="your-project-schedulers",
change_filter=util.ChangeFilter(branch=None),
builderNames=["yourProject"],
properties = {'owner': 'admin'}
)
c['schedulers'] = [hourlyscheduler]
checkout = steps.SVN(repourl='https://svn.host/svn/yourProject/trunk',
mode='full',
method='fresh',
username="user",
password="password",
haltOnFailure=True)
projectHost_build = util.BuildFactory()
cleanProject = steps.ShellCommand(name="Clean",
command=["buildbot/worker_linux/pyinstaller_project", "clean"]
)
buildProject = steps.ShellCommand(name="Build",
command=["buildbot/worker_linux/pyinstaller_project", "build"]
)
doxyProject = steps.ShellCommand(name="Update Docs",
command=["buildbot/worker_linux/gendoc", []]
)
testProject = steps.ShellCommand(name="Tests",
command=["python","tests/utest.py"],
env={'PYTHONPATH': '.'}
)
projectHost_build.addStep(checkout)
projectHost_build.addStep(cleanProject)
projectHost_build.addStep(buildProject)
projectHost_build.addStep(doxyProject)
projectHost_build.addStep(testProject)
c['builders'] = [
util.BuilderConfig(name="yourProject", workername='yourWorkerName', factory=projectHost_build)
]
template_html=u'''
<h4>ะกัะฐััั ะฟะพัััะพะตะฝะฝะพะณะพ ัะตะปะธะทะฐ: {{ summary }}</h4>
<p>ะัะฟะพะปัะทัะตะผัะน ัะตัะฒะธั ะดะปั ะฟะพัััะฐะตะฝะธั: {{ workername }}</p>
<p>ะัะพะตะบั: {{ projects }}</p>
<p>ะะปั ัะพะณะพ ััะพ ะฑั ะฟะพัะผะพััะตัั ะธะฝัะตััะตะนั ัะฟัะฐะฒะปะตะฝะธั ะฟัะพะนะดะธัะต ะฟะพ ัััะปะบะต: {{ buildbot_url }}</p>
<p>ะะปั ัะพะณะพ ััะพ ะฑั ะฟะพัะผะพััะตัั ัะตะทัะปััะฐั ัะฑะพัะบะธ ะฟัะพะนะดะธัะต ะฟะพ ัััะปะบะต: {{ build_url }}</p>
<p>ะัะฟะพะปัะทัั WinSCP ะผะพะถะฝะพ ะฟะพะดะบะปััะธัััั ะบ ัะตัะฒะตัั c ip:xxx.xx.xxx.xx. ะะพะนะดั ะฟะพะด habr/password, ะทะฐะฑัะฐัั ัะพะฑัะฐะฝะฝัะน executable ัะฐะนะป ั ะดะธัะตะบัะพัะธะธ ~/worker/yourProject/build/dist.</p>
<p><b>ะะพัััะพะตะฝะธะต ะฑัะปะพ ะฟัะพะธะทะฒะตะดะตะฝะพ ัะตัะตะท Buildbot</b></p>
'''
sendMessageToAll = reporters.MailNotifier(fromaddr="[email protected]",
sendToInterestedUsers=True,
lookup="your.domain",
relayhost="smtp.your.domain",
smtpPort=1025,
mode="warnings",
extraRecipients=['[email protected]'],
messageFormatter=reporters.MessageFormatter(
template=template_html,
template_type='html',
wantProperties=True,
wantSteps=True)
)
c['services'] = [sendMessageToAll]
c['title'] = "The process of bulding"
c['titleURL'] = "http://project.host:80/"
c['buildbotURL'] = "http://project.host"
c['www'] = dict(port=80,
plugins=dict(waterfall_view={}, console_view={}, grid_view={}))
c['db'] = {
'db_url' : "sqlite:///state.sqlite"
}
ืจืืฉืืช ืืชื ืฆืจืื
ืืฉืื ืืื ืืื ืืืชืืื ืืช ืืฉืืจืืช BuildMasterื
sudo buildbot start /home/habr/master
ืืืืจ ืืื ืืชืื ืืช ืืฉืืจืืช ืขืืื-a
buildbot-worker start /home/habr/worker
ืืึผืึธื! ืขึทืืฉืึธืื Buildbot ืืขืงืื ืืืจ ืฉืื ืืืื ืืืคืขืื ืืืฆืข-ื ืคื ืืื svn, ืืืฆืืข ืฉืืื ืื ืืื ืืืืืงืช ืคืจืืืงื ืขื ืืืจืืืืงืืืจื ืื "ื.
ืืืื ืืชืืจ ืืื ืชืืื ืืช ืฉื ืืืืืจ ืืขืื master.cfg.
6.1 ืืืจื ื-master.cfg ืฉืื
ืชืื ืืื ืืชืืืช ืฉืื master.cfg ืืขืฉื ืฉืืืืืช ืจืืืช, ืืื ืชืืืจืฉ ืงืจืืืช ืงืืืฅ ืืืืื. ืื ืืืืืกื ื BuildMaster-ec ื ืชืื ืืืืื /home/habr/master/twistd.log, ืืืฆื ืขืืื-ื ืขื ื ืชืื ืืืืื /home/habr/worker/twistd.log. ืืืื ืฉืชืงืจื ืืช ืืฉืืืื ืืชืชืงื ืืืชื, ืชืฆืืจื ืืืคืขืื ืืืืฉ ืืช ืืฉืืจืืช BuildMaster-ื. ืื ื ืืื ืื ื ืขืฉื:
sudo buildbot stop /home/habr/master
sudo buildbot upgrade-master /home/habr/master
sudo buildbot start /home/habr/master
6.2 ืขืืืื ืขื svn
svn_poller = changes.SVNPoller(repourl="https://svn.host/svn/yourProject/trunk",
svnuser="user",
svnpasswd="password",
pollinterval=60,
split_file=util.svn.split_file_alwaystrunk
)
c['change_source'] = svn_poller
hourlyscheduler = schedulers.SingleBranchScheduler(
name="your-project-schedulers",
change_filter=util.ChangeFilter(branch=None),
builderNames=["yourProject"],
properties = {'owner': 'admin'}
)
c['schedulers'] = [hourlyscheduler]
checkout = steps.SVN(repourl='https://svn.host/svn/yourProject/trunk',
mode='full',
method='fresh',
username="user",
password="password",
haltOnFailure=True)
ืจืืฉืืช, ืืืื ื ืกืชืื ืขื svn_poller. ืื ืขืืืื ืืืชื ืืืฉืง, ืฉืกืงืจ ืืืืคื ืงืืืข ืืช ืืืืืจ ืคืขื ืืืงื. ืืืงืจื ืืื svn_poller ื ืืืฉ ืจืง ืืกื ืืฃ ืชื ืืืืขื. ืคืจืืืจ ืืกืชืืจื split_file=util.svn.split_file_alwaystrunk ืงืืืข ืืช ืืืืืื: ืืืฆื ืืฉืืืจ ืืช ืืื ื ืืชืืงืืืช svn ืขื ืืขื ืคืื. ืืื ืื ืืฆืืข ืืื ื ืชืืืื ืืืกืืื. ืืชืืจื split_file_alwaystrank ืืคืฉื ืืช ืืชืืืื ืืื ืฉืืืืืจ ืืืื ืจืง ืชื ืืืืขื.
ะ ืืชืืื ืื ืฆืืื ืฉืื ืื ืืกื ืืื ืฉืจืืื ืืื ืืชืืื ืืืฉืืื ืืืื ืกื ืืฃ ืชื ืืืืขื ืืคื ืืกืืฆืืืฆืื ื ืชืื ื ืืจื split_file_alwaystrank. ืชืืืื ืืฉืื ืืืื ื ืชื ืืืืขื, ืืฉืืงื ืืื ื ืขื ืฉื ืืืืื ืฉืื.
ื ืืกืื ืืื ืื ื ืืืฅ ืืื ืฉืืื ืื ืืงืื ืจืฉืืืืช ืืืืืจ ืฉื ืชืืฆืืืช ืื ืืื ืืืืืงืืช ืืืขืืื ืฉื ืืชืืืื.
ืฉืื ืึดืื ืึนืช-a ืืชืฉืืื ืืกืืื ืืืืืง ืืืืืืื ืื ืงืืฆืื ืฉื ืืฆืืื ืืืจืกื ืืืงืืืืช ืฉื ืืืืืจ ืขืืื-ื. ืืื ืชืขืฉื ืืช ืืืืื ืขืืืื. ืืืฆื ืืืืืจ ืืจื ืืคืจืืืจ ืืฆื=ืืื, ืฉืืื=ืืจื... ืคึผึธืจึธืึถืึถืจ haltOnTailure ืืืืจ ืฉืื ืขืืืื ืืืืฆืข ืขื ืฉืืืื, ืื ืืฉ ืืืฉืขืืช ืืช ืื ืชืืืื ืืื ืืื ืืืืืืงื, ืืืืืื ืฉืคืขืืืืช ื ืืกืคืืช ืืื ื ืืืืื ืืืช.
6.3 ืืืชื ืืืื: ืืชืืื ืืืกืืืื ืืืฆืืืจ
ืืชืืื ืืื ืฉืืจืืช ืืฉืืืืช ืืืืขืืช ืืืืื.
template_html=u'''
<h4>ะกัะฐััั ะฟะพัััะพะตะฝะฝะพะณะพ ัะตะปะธะทะฐ: {{ summary }}</h4>
<p>ะัะฟะพะปัะทัะตะผัะน ัะตัะฒะธั ะดะปั ะฟะพัััะฐะตะฝะธั: {{ workername }}</p>
<p>ะัะพะตะบั: {{ projects }}</p>
<p>ะะปั ัะพะณะพ ััะพ ะฑั ะฟะพัะผะพััะตัั ะธะฝัะตััะตะนั ัะฟัะฐะฒะปะตะฝะธั ะฟัะพะนะดะธัะต ะฟะพ ัััะปะบะต: {{ buildbot_url }}</p>
<p>ะะปั ัะพะณะพ ััะพ ะฑั ะฟะพัะผะพััะตัั ัะตะทัะปััะฐั ัะฑะพัะบะธ ะฟัะพะนะดะธัะต ะฟะพ ัััะปะบะต: {{ build_url }}</p>
<p>ะัะฟะพะปัะทัั WinSCP ะผะพะถะฝะพ ะฟะพะดะบะปััะธัััั ะบ ัะตัะฒะตัั c ip:xxx.xx.xxx.xx. ะะพะนะดั ะฟะพะด habr/password, ะทะฐะฑัะฐัั ัะพะฑัะฐะฝะฝัะน executable ัะฐะนะป ั ะดะธัะตะบัะพัะธะธ ~/worker/yourProject/build/dist.</p>
<p><b>ะะพัััะพะตะฝะธะต ะฑัะปะพ ะฟัะพะธะทะฒะตะดะตะฝะพ ัะตัะตะท Buildbot</b></p>
'''
sendMessageToAll = reporters.MailNotifier(fromaddr="[email protected]",
sendToInterestedUsers=True,
lookup="your.domain",
relayhost="smtp.your.domain",
smtpPort=1025,
mode="warnings",
extraRecipients=['[email protected]'],
messageFormatter=reporters.MessageFormatter(
template=template_html,
template_type='html',
wantProperties=True,
wantSteps=True)
)
c['services'] = [sendMessageToAll]
ืืื ืืืื ืืฉืืื ืืืืขืืช
MailNotifier ืืฉืชืืฉ ืืืื"ื ืืื ืืฉืืื ืืืืขืืช.
template_html ืืืืืจ ืืช ืชืื ืืช ืืืงืกื ืขืืืจ ืื ืืืืืืจ. HTML ืืฉืืฉ ืืืฆืืจืช ืกืืืื. ืื ืืฉืชื ื ืขื ืืื ืืื ืืข
MailNotifier ืืืื ืืช ืืืืขืื ืื ืืืืื.
fromaddr โ ืืืชืืืช ืืื ื ืืืื ืืงืืื ืืช ืื ืืืืืืจ.
sendToInterestedUsers=True ืฉืืื ืืืืขื ืืืขืืื ืืืืฉืชืืฉ ืฉืขืฉื ืืืฆืข.
ืืืืงื - ืกืืืืช ืฉืืฉ ืืืืกืืฃ ืืฉืืืช ืืืฉืชืืฉืื ืืืงืืืื ืืช ืื ืืืืืืจ. ืื ืื ืื ืืืฆื ืืงืื ืืืฉืชืืฉ ืืช ืื ืืืืืืจ ืืืชืืืช [ืืืื ืืืื"ื].
ืืืจื ืืืกืจ ืืฆืืื ืืช ืฉื ืืืืจื ืฉืื ืืฉืจืช ื ืคืชื SMTP, smptPort ืืฆืืื ืืช ืืกืคืจ ืืืฆืืื ืฉืืืืื SMTP ืฉืจืช
mode="ืืืืจื" ืืืืจ ืฉืืืืืืจ ืฆืจืื ืืืืขืฉืืช ืจืง ืื ืืฉ ืืคืืืช ืฉืื ืืื ืึดืื ืึนืช-a, ืฉืืกืชืืื ืืืืฉืืื ืืกืืืืก ืื ืืืืืจื. ืืืงืจื ืฉื ืืฆืืื, ืืื ืฆืืจื ืืฉืืื ื ืืืืืืจ.
ื ืืขื ืื ื ืืกืคืื ืืืื ืจืฉืืื ืฉื ืื ืฉืื ืฉืืืืื ืืฉ ืืฉืืื ืืช ืืืืืืจ ืื ืืกืฃ ืืืขืืื ืืืื ืฉืืืฆืข ืืช ืืืฆืข.
messageFormatter ืืื ืืืืืืงื ืืืฆืืื ืืช ืคืืจืื ืืืืืขื, ืืชืื ืืช ืฉืื ืืงืืืฆืช ืืฉืชื ืื ืืืื ืื ื jinja2. ืืคืฉืจืืืืช ืืืื wantProperties=ื ืืื ะธ wantSteps=ื ืืื ืืืืืืจ ืงืืืฆื ืื ืฉื ืืฉืชื ืื ืืืื ืื.
with['services']=[sendMessageToAll] ืืกืคืง ืจืฉืืื ืฉื ืฉืืจืืชืื, ืืื ืืื ืืืื ืฉืื ื ืืชื.
ืขืฉืื ื ืืืช! ืืื ืืื
ืืฆืจื ื ืชืฆืืจื ืืฉืื ื ืืจืืื ื ืืช ืืคืื ืงืฆืืื ืืืืช ืฉืืื ืืกืืืืช ืื. BuildBot. ืื, ืืืขืชื, ืืกืคืืง ืืื ืืืืื ืื ืืืื ืืื ื ืืืฅ ืืืฆืืจืช ืืคืจืืืงื ืฉืื. ืืื ืืชื ืืขืื ืืื ืื? ืืื ืื ืืืขืื ืื? ืืื ื ืื ืืขืืื ืืืชื? ืื ืื ื ืื ืืืชื ืืช ืืืืืจ ืืื ืืฉืืื.
ืืขืื. ืืืืชื ืจืืฆื ืฉืืงืืืื ืืืงืฆืืขืืช ืืฉืชืืฉืช BuildBot, ืืคื ืจืื ืืืชืจ, ืืืจืืืื ืชืืจืืื, ืืืื ืขืื ืืืืืืืช.
ืชืืื ืืืืืื ืขื ืชืฉืืืช ืืื. ืืืฆืืื.
ืืงืืจ: www.habr.com