You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Buildbot调度器级别过滤构建器时遇配置错误求助

Buildbot自定义BuilderConfig类引发的配置错误及解决方法

问题背景

作为Buildbot新手,希望在调度器级别过滤构建器,避免默认触发所有关联构建。尝试通过继承util.BuilderConfig类创建Builder_A、Builder_B自定义类,重写build方法实现过滤,代码如下:

from buildbot.plugins import *
c = BuildmasterConfig = {}
c['workers'] = [worker.Worker("example-worker", "pass"), worker.Worker("example-worker1", "pass")]

class Builder_A(util.BuilderConfig):
    name = "runtests"
    workernames = ["example-worker"]
    factory = util.BuildFactory()
    factory.addStep(steps.ShellCommand(command=["echo", "all"]))
    def build(self, request):
            if request.source.changes:
                    for change in request.source.changes:
                            if not change.files or all(file.endswith('.c') or file.endswith('.h') for file in change.files):
                                    return super().build(request)
                    return None
            else:
                    return None

class Builder_B(util.BuilderConfig):
    name = "runtests1"
    workernames = ["example-worker1"]
    factory = util.BuildFactory()
    factory.addStep(steps.ShellCommand(command=["echo", "Source code"]))
    def build(self, request):
            if request.source.changes:
                    for change in request.source.changes:
                            if not change.files or all(file.endswith('.py') for file in change.files):
                                    return super().build(request)
                    return None
            else:
                    return None

c['protocols'] = {'pb': {'port': 9989}}
c['change_source'] = []
c['change_source'].append(changes.GitPoller(
    'https://github.com/hello/Sample_Project.git',
    workdir='gitpoller-workdir', branch='master',
    pollInterval=30))
c['builders'] = [Builder_A(), Builder_B()]
c['schedulers'] = []
c['schedulers'].append(schedulers.SingleBranchScheduler(
                        name="all",
                        change_filter=util.ChangeFilter(branch='master'),
                        treeStableTimer=None,
                        builderNames=["runtests","runtests1"]))
c['schedulers'].append(schedulers.ForceScheduler(
                        name="force",
                        builderNames=["runtests"]))
c['services'] = []
c['title'] = "Hello World CI"
c['titleURL'] = "https://buildbot.github.io/hello-world/"
c['buildbotURL'] = "http://10.20.212.19:8010/"
c['www'] = dict(port=8010,
            plugins=dict(waterfall_view={}, console_view={}, grid_view={})) 
c['db'] = {
'db_url' : "sqlite:///state.sqlite",
}

出现的配置错误

Configuration Errors:
builder's name is required
builder '<unknown>' has no factory
builder '<unknown>': at least one workername is required
builder's name is required
builder '<unknown>' has no factory
builder '<unknown>': at least one workername is required
builder(s) <unknown> have no schedulers to drive them
Unknown builder 'runtests' in scheduler 'all'
Unknown builder 'runtests1' in scheduler 'all'
Unknown builder 'runtests' in scheduler 'force'
duplicate builder name '<unknown>'
duplicate builder builddir '_unknown_'

使用环境:Buildbot 1.1.1-3ubuntu5,Twisted 17.9.0,Ubuntu 18.04

错误原因分析

  1. BuilderConfig属性初始化方式错误:util.BuilderConfig的核心属性(如name、workernames、factory)需要通过构造函数参数传递,而非类属性定义。直接在子类中定义类属性,父类构造函数无法读取这些属性,导致实例化后关键配置未初始化,Buildbot识别不出构建器的基本信息。
  2. 重写build方法的思路错误:BuilderConfig的build方法并非用于过滤构建请求的官方入口,该方法不支持通过返回None跳过构建,正确的过滤逻辑应通过调度器change_filter或构建器buildFilter实现。

解决方法

方案1:构建器级别过滤(使用原生BuilderConfig+buildFilter)

无需继承BuilderConfig,直接创建实例,通过buildFilter为每个构建器添加自定义过滤逻辑:

from buildbot.plugins import *
c = BuildmasterConfig = {}
c['workers'] = [worker.Worker("example-worker", "pass"), worker.Worker("example-worker1", "pass")]

# 定义构建工厂
factory_a = util.BuildFactory()
factory_a.addStep(steps.ShellCommand(command=["echo", "all"]))

factory_b = util.BuildFactory()
factory_b.addStep(steps.ShellCommand(command=["echo", "Source code"]))

# 自定义构建过滤函数
def filter_c_h_changes(request):
    if not request.source.changes:
        return False
    for change in request.source.changes:
        if not change.files or all(file.endswith('.c') or file.endswith('.h') for file in change.files):
            return True
    return False

def filter_py_changes(request):
    if not request.source.changes:
        return False
    for change in request.source.changes:
        if not change.files or all(file.endswith('.py') for file in change.files):
            return True
    return False

# 创建构建器实例
c['builders'] = [
    util.BuilderConfig(
        name="runtests",
        workernames=["example-worker"],
        factory=factory_a,
        buildFilter=filter_c_h_changes
    ),
    util.BuilderConfig(
        name="runtests1",
        workernames=["example-worker1"],
        factory=factory_b,
        buildFilter=filter_py_changes
    )
]

# 其余配置保持不变
c['protocols'] = {'pb': {'port': 9989}}
c['change_source'] = []
c['change_source'].append(changes.GitPoller(
    'https://github.com/hello/Sample_Project.git',
    workdir='gitpoller-workdir', branch='master',
    pollInterval=30))
c['schedulers'] = []
c['schedulers'].append(schedulers.SingleBranchScheduler(
                        name="all",
                        change_filter=util.ChangeFilter(branch='master'),
                        treeStableTimer=None,
                        builderNames=["runtests","runtests1"]))
c['schedulers'].append(schedulers.ForceScheduler(
                        name="force",
                        builderNames=["runtests"]))
c['services'] = []
c['title'] = "Hello World CI"
c['titleURL'] = "https://buildbot.github.io/hello-world/"
c['buildbotURL'] = "http://10.20.212.19:8010/"
c['www'] = dict(port=8010,
            plugins=dict(waterfall_view={}, console_view={}, grid_view={})) 
c['db'] = {
'db_url' : "sqlite:///state.sqlite",
}

方案2:调度器级别过滤(为不同构建器配置专属调度器)

在调度器层面控制触发逻辑,为每个构建器设置匹配对应文件类型的change_filter:

from buildbot.plugins import *
c = BuildmasterConfig = {}
c['workers'] = [worker.Worker("example-worker", "pass"), worker.Worker("example-worker1", "pass")]

# 定义构建工厂
factory_a = util.BuildFactory()
factory_a.addStep(steps.ShellCommand(command=["echo", "all"]))

factory_b = util.BuildFactory()
factory_b.addStep(steps.ShellCommand(command=["echo", "Source code"]))

# 创建构建器实例
c['builders'] = [
    util.BuilderConfig(
        name="runtests",
        workernames=["example-worker"],
        factory=factory_a
    ),
    util.BuilderConfig(
        name="runtests1",
        workernames=["example-worker1"],
        factory=factory_b
    )
]

# 自定义变更过滤函数
def c_h_filter(change):
    if not change.files:
        return True
    return all(file.endswith('.c') or file.endswith('.h') for file in change.files)

def py_filter(change):
    if not change.files:
        return True
    return all(file.endswith('.py') for file in change.files)

# 配置对应调度器
c['protocols'] = {'pb': {'port': 9989}}
c['change_source'] = []
c['change_source'].append(changes.GitPoller(
    'https://github.com/hello/Sample_Project.git',
    workdir='gitpoller-workdir', branch='master',
    pollInterval=30))
c['schedulers'] = []
# 匹配C/H文件变更的调度器
c['schedulers'].append(schedulers.SingleBranchScheduler(
                        name="c_h_scheduler",
                        change_filter=util.ChangeFilter(branch='master', filter_fn=c_h_filter),
                        treeStableTimer=None,
                        builderNames=["runtests"]))
# 匹配Python文件变更的调度器
c['schedulers'].append(schedulers.SingleBranchScheduler(
                        name="py_scheduler",
                        change_filter=util.ChangeFilter(branch='master', filter_fn=py_filter),
                        treeStableTimer=None,
                        builderNames=["runtests1"]))
c['schedulers'].append(schedulers.ForceScheduler(
                        name="force",
                        builderNames=["runtests"]))
c['services'] = []
c['title'] = "Hello World CI"
c['titleURL'] = "https://buildbot.github.io/hello-world/"
c['buildbotURL'] = "http://10.20.212.19:8010/"
c['www'] = dict(port=8010,
            plugins=dict(waterfall_view={}, console_view={}, grid_view={})) 
c['db'] = {
'db_url' : "sqlite:///state.sqlite",
}

关键说明

  • BuilderConfig正确用法:必须通过构造函数参数传递name、workernames、factory等核心配置,不能用类属性替代。
  • 过滤逻辑官方入口:优先使用buildFilter(构建器级别)或change_filter(调度器级别)实现过滤,这是Buildbot官方推荐的标准方式。

内容的提问来源于stack exchange,提问作者vam

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.25 12:32:01