Skip to content

Commit 5acfc8a

Browse files
committed
feat(github-matrix): group evaluation errors by message
Refactor error handling to collect and group evaluation errors similar to warnings. Errors with the same message are now displayed together with a list of affected attributes.
1 parent 2f17aa7 commit 5acfc8a

File tree

1 file changed

+46
-20
lines changed

1 file changed

+46
-20
lines changed

nix/packages/github-matrix/github_matrix.py

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
get_args,
2121
)
2222

23-
from github_action_utils import debug, error, set_output, warning
23+
from github_action_utils import debug, notice, error, set_output, warning
2424
from result import Err, Ok, Result
2525

2626
System = Literal["x86_64-linux", "aarch64-linux", "aarch64-darwin"]
@@ -60,6 +60,13 @@ class GitHubActionPackage(TypedDict):
6060
postgresql_version: NotRequired[str]
6161

6262

63+
class NixEvalError(TypedDict):
64+
"""Error information from nix evaluation."""
65+
66+
attr: str
67+
error: str
68+
69+
6370
BUILD_RUNNER_MAP: Dict[RunnerType, Dict[System, RunsOnConfig]] = {
6471
"ephemeral": {
6572
"aarch64-linux": {
@@ -107,24 +114,25 @@ def build_nix_eval_command(max_workers: int, flake_outputs: List[str]) -> List[s
107114

108115
def parse_nix_eval_line(
109116
line: str, drv_paths: Set[str]
110-
) -> Result[Optional[NixEvalJobsOutput], str]:
117+
) -> Result[Optional[NixEvalJobsOutput], NixEvalError]:
111118
"""Parse a single line of nix-eval-jobs output.
112119
113120
Returns:
114121
Ok(package_data) if successful (None for empty/duplicate lines)
115-
Err(error_message) if a nix evaluation error occurred
122+
Err(NixEvalError) if a nix evaluation error occurred
116123
"""
117124
if not line.strip():
118125
return Ok(None)
119126

120127
try:
121128
data: NixEvalJobsOutput = json.loads(line)
122129
if "error" in data:
123-
error_msg = (
124-
f"Error in nix-eval-jobs output for {data['attr']}: {data['error']}"
125-
)
126-
error(error_msg, title="Nix Evaluation Error")
127-
return Err(error_msg)
130+
error_msg = data["error"]
131+
# Strip the redundant first line if it contains "does not have valid outputs"
132+
error_lines = error_msg.split("\n")
133+
if len(error_lines) > 1 and "does not have valid outputs" in error_lines[0]:
134+
error_msg = "\n".join(error_lines[1:]).strip()
135+
return Err({"attr": data["attr"], "error": error_msg})
128136
if data["drvPath"] in drv_paths:
129137
return Ok(None)
130138
drv_paths.add(data["drvPath"])
@@ -136,11 +144,11 @@ def parse_nix_eval_line(
136144

137145
def run_nix_eval_jobs(
138146
cmd: List[str],
139-
) -> Tuple[List[NixEvalJobsOutput], List[str], bool]:
140-
"""Run nix-eval-jobs and return parsed package data, warnings, and error status.
147+
) -> Tuple[List[NixEvalJobsOutput], List[str], List[NixEvalError]]:
148+
"""Run nix-eval-jobs and return parsed package data, warnings, and errors.
141149
142150
Returns:
143-
Tuple of (packages, warnings_list, had_errors)
151+
Tuple of (packages, warnings_list, errors_list)
144152
"""
145153
debug(f"Running command: {' '.join(cmd)}")
146154

@@ -152,11 +160,11 @@ def run_nix_eval_jobs(
152160
# Parse stdout for packages
153161
packages: List[NixEvalJobsOutput] = []
154162
drv_paths: Set[str] = set()
155-
had_errors = False
163+
errors_list: List[NixEvalError] = []
156164
for line in stdout_data.splitlines():
157165
result = parse_nix_eval_line(line, drv_paths)
158166
if result.is_err():
159-
had_errors = True
167+
errors_list.append(result.err_value)
160168
elif result.ok_value is not None:
161169
packages.append(result.ok_value)
162170

@@ -174,7 +182,7 @@ def run_nix_eval_jobs(
174182
title="Process Failure",
175183
)
176184

177-
return packages, warnings_list, had_errors
185+
return packages, warnings_list, errors_list
178186

179187

180188
def is_extension_pkg(pkg: NixEvalJobsOutput) -> bool:
@@ -260,8 +268,8 @@ def main() -> None:
260268

261269
cmd = build_nix_eval_command(max_workers, args.flake_outputs)
262270

263-
# Run evaluation and collect packages and warnings
264-
packages, warnings_list, had_errors = run_nix_eval_jobs(cmd)
271+
# Run evaluation and collect packages, warnings, and errors
272+
packages, warnings_list, errors_list = run_nix_eval_jobs(cmd)
265273
gh_action_packages = sort_pkgs_by_closures(packages)
266274

267275
def clean_package_for_output(pkg: NixEvalJobsOutput) -> GitHubActionPackage:
@@ -318,13 +326,31 @@ def clean_package_for_output(pkg: NixEvalJobsOutput) -> GitHubActionPackage:
318326
else:
319327
warning(warn_msg, title="Nix Evaluation Warning")
320328

321-
# Output matrix to GitHub Actions
322-
debug(f"Generated GitHub Actions matrix: {json.dumps(gh_output, indent=2)}")
323-
set_output("matrix", json.dumps(gh_output))
329+
if errors_list:
330+
# Group errors by error message
331+
errors_by_message: Dict[str, List[str]] = defaultdict(list)
332+
for err in errors_list:
333+
errors_by_message[err["error"]].append(err["attr"])
334+
335+
for error_msg, attrs in errors_by_message.items():
336+
if len(attrs) > 1:
337+
error(
338+
f"{error_msg}\nAffected attributes: {', '.join(attrs)}",
339+
title="Nix Evaluation Error",
340+
)
341+
else:
342+
error(
343+
f"{error_msg}\nAttribute: {attrs[0]}",
344+
title="Nix Evaluation Error",
345+
)
324346

325347
# Exit with error code if any evaluation errors occurred
326-
if had_errors:
348+
if errors_list:
327349
sys.exit(1)
350+
else:
351+
# Output matrix to GitHub Actions
352+
notice(f"Generated GitHub Actions matrix: {json.dumps(gh_output, indent=2)}")
353+
set_output("matrix", json.dumps(gh_output))
328354

329355

330356
if __name__ == "__main__":

0 commit comments

Comments
 (0)