Skip to content

Commit e665daa

Browse files
MagicRBMic92
authored andcommitted
Actually add failed_builds.py and add time of first failure to the "database"
Signed-off-by: magic_rb <[email protected]>
1 parent eb1d610 commit e665daa

File tree

2 files changed

+82
-18
lines changed

2 files changed

+82
-18
lines changed

buildbot_nix/__init__.py

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
from collections import defaultdict
88
from collections.abc import Generator
99
from dataclasses import dataclass
10+
from datetime import UTC, datetime
1011
from pathlib import Path
1112
from typing import TYPE_CHECKING, Any
1213

@@ -40,9 +41,7 @@
4041
from twisted.logger import Logger
4142
from twisted.python.failure import Failure
4243

43-
from . import models
44-
from pydantic import TypeAdapter
45-
from . import failed_builds
44+
from . import failed_builds, models
4645
from .gitea_projects import GiteaBackend
4746
from .github_projects import (
4847
GithubBackend,
@@ -196,13 +195,14 @@ def schedule_eval_failure(
196195
return (self.failed_eval_scheduler, props)
197196

198197
def schedule_cached_failure(
199-
self, job: NixEvalJobSuccess, report_status: bool
198+
self, job: NixEvalJobSuccess, report_status: bool, first_failure: datetime
200199
) -> tuple[str, Properties]:
201200
source = "nix-eval-nix"
202201

203202
props = BuildTrigger.set_common_properties(
204203
Properties(), source, job, report_status
205204
)
205+
props.setProperty("first_failure", str(first_failure), source)
206206

207207
return (self.cached_failure_scheduler, props)
208208

@@ -442,32 +442,29 @@ def run(self) -> Generator[Any, Any, None]:
442442
# check which jobs should be scheduled now
443443
schedule_now = []
444444
for build in list(build_schedule_order):
445+
failed_build = failed_builds.check_build(build.drvPath)
445446
if job_closures.get(build.drvPath):
446447
pass
447-
elif (
448-
failed_builds.check_build(build.drvPath)
449-
and self.build.reason != "rebuild"
450-
):
451-
failed_builds.remove_build(build.drvPath)
448+
elif failed_build is not None and self.build.reason != "rebuild":
452449
scheduler_log.addStdout(
453-
f"\t- skipping {build.attr} due to cached failure\n"
450+
f"\t- skipping {build.attr} due to cached failure, first failed at {failed_build.time}\n"
454451
)
455452
build_schedule_order.remove(build)
456453

457454
brids, results_deferred = yield self.schedule(
458455
ss_for_trigger,
459-
*self.schedule_cached_failure(build, self.report_status),
456+
*self.schedule_cached_failure(
457+
build, self.report_status, failed_build.time
458+
),
460459
)
461460
scheduled.append(
462461
BuildTrigger.ScheduledJob(build, brids, results_deferred)
463462
)
464463
self.brids.extend(brids.values())
465-
elif (
466-
failed_builds.check_build(build.drvPath)
467-
and self.build.reason == "rebuild"
468-
):
464+
elif failed_build is not None and self.build.reason == "rebuild":
465+
failed_builds.remove_build(build.drvPath)
469466
scheduler_log.addStdout(
470-
f"\t- not skipping {build.attr} with cached failure due to rebuild\n"
467+
f"\t- not skipping {build.attr} with cached failure due to rebuild, first failed at {failed_build.time}\n"
471468
)
472469

473470
build_schedule_order.remove(build)
@@ -516,7 +513,7 @@ def run(self) -> Generator[Any, Any, None]:
516513

517514
# if it failed, remove all dependent jobs, schedule placeholders and add them to the list of scheduled jobs
518515
if result != SUCCESS:
519-
failed_builds.add_build(job.drvPath)
516+
failed_builds.add_build(job.drvPath, datetime.now(tz=UTC))
520517

521518
removed = self.get_failed_dependents(
522519
job, build_schedule_order, job_closures
@@ -707,7 +704,13 @@ def run(self) -> Generator[Any, object, int]:
707704
# show eval error
708705
error_log: StreamLog = yield self.addLog("nix_error")
709706
error_log.addStderr(
710-
f"{attr} was failed because it has failed previous and its failure has been cached.\n"
707+
"\n".join(
708+
[
709+
f"{attr} was failed because it has failed previously and its failure has been cached.",
710+
f" first failure time: {self.getProperty('first_failure')}",
711+
]
712+
)
713+
+ "\n"
711714
)
712715
return util.FAILURE
713716

buildbot_nix/failed_builds.py

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
import dbm
2+
from datetime import datetime
3+
from pathlib import Path
4+
from typing import TYPE_CHECKING, Any
5+
6+
from pydantic import BaseModel
7+
8+
if TYPE_CHECKING:
9+
database: None | dbm._Database = None
10+
else:
11+
database: Any = None
12+
13+
14+
class FailedBuildsError(Exception):
15+
pass
16+
17+
18+
class FailedBuild(BaseModel):
19+
derivation: str
20+
time: datetime
21+
22+
23+
DB_NOT_INIT_MSG = "Database not initialized"
24+
25+
26+
def initialize_database(db_path: Path) -> None:
27+
global database # noqa: PLW0603
28+
29+
if not database:
30+
database = dbm.open(str(db_path), "c")
31+
32+
33+
def add_build(derivation: str, time: datetime) -> None:
34+
global database # noqa: PLW0602
35+
36+
if database is not None:
37+
database[derivation] = FailedBuild(
38+
derivation=derivation, time=time
39+
).model_dump_json()
40+
else:
41+
raise FailedBuildsError(DB_NOT_INIT_MSG)
42+
43+
44+
def check_build(derivation: str) -> FailedBuild | None:
45+
global database # noqa: PLW0602
46+
47+
if database is not None:
48+
if derivation in database:
49+
# TODO create dummy if deser fails?
50+
return FailedBuild.model_validate_json(database[derivation])
51+
return None
52+
raise FailedBuildsError(DB_NOT_INIT_MSG)
53+
54+
55+
def remove_build(derivation: str) -> None:
56+
global database # noqa: PLW0602
57+
58+
if database is not None:
59+
del database[derivation]
60+
else:
61+
raise FailedBuildsError(DB_NOT_INIT_MSG)

0 commit comments

Comments
 (0)