Skip to content

Commit

Permalink
Switch to an attrset of branch rules
Browse files Browse the repository at this point in the history
Signed-off-by: magic_rb <[email protected]>
  • Loading branch information
MagicRB committed Oct 31, 2024
1 parent 8cb9acb commit f7a057b
Show file tree
Hide file tree
Showing 3 changed files with 138 additions and 66 deletions.
58 changes: 29 additions & 29 deletions buildbot_nix/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -738,13 +738,13 @@ class UpdateBuildOutput(steps.BuildStep):

project: GitProject
path: Path
branch_config: models.BranchConfig
branch_config: models.BranchConfigDict

def __init__(
self,
project: GitProject,
path: Path,
branch_config: models.BranchConfig,
branch_config: models.BranchConfigDict,
**kwargs: Any,
) -> None:
super().__init__(**kwargs)
Expand Down Expand Up @@ -774,7 +774,7 @@ def join_all_traversalsafe(self, root: Path, *paths: str) -> Path:
async def run(self) -> int:
props = self.build.getProperties()

if self.branch_config.do_register_gcroot(
if not self.branch_config.do_copy_outputs(
self.project.default_branch, props.getProperty("branch")
):
return util.SKIPPED
Expand Down Expand Up @@ -947,12 +947,12 @@ def nix_eval_config(


async def do_register_gcroot_if(
s: steps.BuildStep, branch_config: models.BranchConfig
s: steps.BuildStep, branch_config: models.BranchConfigDict
) -> bool:
gc_root = await util.Interpolate(
"/nix/var/nix/gcroots/per-user/buildbot-worker/%(prop:project)s/%(prop:attr)s"
).getRenderingFor(s.getProperties())
out_path = await util.Property("out_path").getRenderingFor(s.getProperties())
out_path = await s.getProperty("out_path")

return branch_config.do_register_gcroot(
s.getProperty("default_branch"), s.getProperty("branch")
Expand All @@ -965,7 +965,7 @@ def nix_build_steps(
project: GitProject,
worker_names: list[str],
post_build_steps: list[steps.BuildStep],
branch_config: models.BranchConfig,
branch_config: models.BranchConfigDict,
outputs_path: Path | None = None,
global_do_step_if: Callable[[steps.BuildStep], bool] | bool = True,
) -> list[steps.BuildStep]:
Expand Down Expand Up @@ -1138,7 +1138,7 @@ def nix_cached_failure_config(
def nix_skipped_build_config(
project: GitProject,
worker_names: list[str],
branch_config: models.BranchConfig,
branch_config: models.BranchConfigDict,
outputs_path: Path | None = None,
) -> BuilderConfig:
"""Dummy builder that is triggered when a build is skipped."""
Expand Down Expand Up @@ -1231,7 +1231,7 @@ def config_for_project(
post_build_steps: list[steps.BuildStep],
job_report_limit: int | None,
failed_builds_db: FailedBuildDB,
branch_config: models.BranchConfig,
branch_config: models.BranchConfigDict,
outputs_path: Path | None = None,
) -> None:
config["projects"].append(Project(project.name))
Expand All @@ -1250,29 +1250,29 @@ def config_for_project(
)
]
)
if branch_config.build_prs:
config["schedulers"].extend(
[
# this is compatible with bors or github's merge queue
schedulers.SingleBranchScheduler(
name=f"{project.project_id}-merge-queue",
change_filter=util.ChangeFilter(
repository=project.url,
branch_re="(gh-readonly-queue/.*|staging|trying)",
),
builderNames=[f"{project.name}/nix-eval"],
config["schedulers"].extend(
[
# this is compatible with bors or github's merge queue
schedulers.SingleBranchScheduler(
name=f"{project.project_id}-merge-queue",
change_filter=util.ChangeFilter(
# TODO add filter
repository=project.url,
branch_re="(gh-readonly-queue/.*|staging|trying)",
),
# build all pull requests
schedulers.SingleBranchScheduler(
name=f"{project.project_id}-prs",
change_filter=util.ChangeFilter(
repository=project.url,
category="pull",
),
builderNames=[f"{project.name}/nix-eval"],
builderNames=[f"{project.name}/nix-eval"],
),
# build all pull requests
schedulers.SingleBranchScheduler(
name=f"{project.project_id}-prs",
change_filter=util.ChangeFilter(
repository=project.url,
category="pull",
),
]
)
builderNames=[f"{project.name}/nix-eval"],
),
]
)
config["schedulers"].extend(
[
# this is triggered from `nix-eval`
Expand Down
51 changes: 41 additions & 10 deletions buildbot_nix/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from collections.abc import Mapping
from enum import Enum
from pathlib import Path
from typing import Any
from typing import Any, Callable

from buildbot.plugins import steps, util
from pydantic import BaseModel, ConfigDict, Field, TypeAdapter
Expand Down Expand Up @@ -174,20 +174,51 @@ def maybe_interpolate(value: str | Interpolate) -> str | util.Interpolate:


class BranchConfig(BaseModel):
build_prs: bool
primary: re.Pattern
secondary: re.Pattern
match_glob: str = Field(validation_alias = "matchGlob")
register_gcroots: bool = Field(validation_alias = "registerGCRoots")
copy_outputs: bool = Field(validation_alias = "copyOutputs")

def do_run(self, default_branch: str, branch: str) -> bool:
match_regex: re.Pattern = Field(match_glob, exclude = True, frozen = True)

def __or__(self, other: BranchConfig) -> BranchConfig:
assert(self.match_glob == other.match_glob)
assert(self.match_regex == other.match_regex)

return BranchConfig(
match_glob = self.match_glob,
register_gcroots = self.register_gcroots or other.register_gcroots,
copy_outputs = self.copy_outputs or other.copy_outputs,
match_regex = self.match_regex
)

class BranchConfigDict(dict[str, BranchConfig]):
def lookup_branch_config(self, branch: str) -> BranchConfig | None:
ret = None
for branch_config in self.values():
if branch_config.match_regex.fullmatch(branch):
if ret is None:
ret = branch_config
else:
ret |= branch_config
return ret

def check_lookup(self, default_branch: str, branch: str, fn: Callable[[BranchConfig], bool]) -> bool:
branch_config = self.lookup_branch_config(branch)
return (
branch == default_branch
or self.primary.fullmatch(branch) is not None
or self.secondary.fullmatch(branch) is not None
branch == default_branch or (
branch_config is not None
and fn(branch_config)
)
)

def do_run(self, default_branch: str, branch: str) -> bool:
return self.check_lookup(default_branch, branch, lambda _: True)

def do_register_gcroot(self, default_branch: str, branch: str) -> bool:
return branch == default_branch or self.primary.fullmatch(branch) is not None
return self.check_lookup(default_branch, branch, lambda bc: bc.register_gcroots)

def do_copy_outputs(self, default_branch: str, branch: str) -> bool:
return self.check_lookup(default_branch, branch, lambda bc: bc.copy_outputs)

class BuildbotNixConfig(BaseModel):
db_url: str
Expand All @@ -209,7 +240,7 @@ class BuildbotNixConfig(BaseModel):
post_build_steps: list[PostBuildStep]
job_report_limit: int | None
http_basic_auth_password_file: Path | None
branches: BranchConfig
branches: BranchConfigDict

@property
def nix_workers_secret(self) -> str:
Expand Down
95 changes: 68 additions & 27 deletions nix/master.nix
Original file line number Diff line number Diff line change
Expand Up @@ -493,37 +493,78 @@ in
default = 50;
};

branches = {
build_prs = lib.mkOption {
type = lib.types.bool;
description = ''
When enabled buildbot-nix will schedule builds for every PR opened.
It will however not register GC roots.
'';
default = true;
};
branches = lib.mkOption {
type = lib.types.attrsOf lib.types.submodule {
option = {
matchGlob = lib.mkOption {
type = lib.types.str;
description = ''
A glob specifying which branches to apply this rule to.
'';
};

primary = lib.mkOption {
type = lib.types.str;
description = ''
A `re` Python compatible regex specifying which additional branches should
buildbot-nix consider in addition to the default branch. Like for the default
branch, buildbot-nix will register GC roots for any builds it performs for this
branch. An empty regex means to not consider any additional branches.
'';
default = "";
};
registerGCRoots = lib.mkOption {
type = lib.types.bool;
description = ''
Whether to register gcroots for branches matching this glob.
'';
default = false;
};

secondary = lib.mkOption {
type = lib.types.str;
description = ''
Refer to the description of `branches.primary`, the only difference between it and this
option is that buildbot-nix will *not* register GC roots for any builds it performs,
similarly to how it behaves for PRs.
'';
default = "";
copyOutputs = lib.mkOption {
type = lib.types.bool;
description = ''
Whether to copy outputs for branches matching this glob.
'';
default = false;
};
};
};
default = {};
description = ''
An attrset of branch rules, each rule specifies which branches it should apply to using the
`matchGlob` option and then the corresponding settings are applied to the matched branches.
If multiple rules match a given branch, the rules are `or`-ed together, by `or`-ing each
individual boolean option of all matching rules. Take the following as example:
```
{
rule1 = {
matchGlob = "f*";
build_prs = true;
register_gcroots = false;
copy_outputs = false;
}
rule2 = {
matchGlob = "foo";
build_prs = false;
register_gcroots = true;
copy_outputs = false;
}
}
```
This example will result in `build_prs` and `register_gcroots` both being considered `true`,
but `copy_outputs` being `false` for the branch `foo`.
The default branches of all repos are considered to be matching of a rule setting all the options
to `true`.
'';
};
example = lib.literalExpression ''
{
rule1 = {
matchGlob = "f*";
build_prs = true;
register_gcroots = false;
copy_outputs = false;
}
rule2 = {
matchGlob = "foo";
build_prs = false;
register_gcroots = true;
copy_outputs = false;
}
}
'';
};
};
config = lib.mkMerge [
Expand Down

0 comments on commit f7a057b

Please sign in to comment.