Skip to content

Commit 01d95e7

Browse files
committed
feat(github-matrix): integrate github-action-utils for better error visibility
Integrates github-action-utils library to improve error and warning visibility in GitHub Actions UI through workflow command annotations.
1 parent 4e91e21 commit 01d95e7

File tree

3 files changed

+107
-61
lines changed

3 files changed

+107
-61
lines changed

.github/workflows/nix-eval.yml

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,4 @@ jobs:
3131
name: Generate Nix Matrix
3232
run: |
3333
set -Eeu -o pipefail
34-
result=$(nix run --accept-flake-config .\#github-matrix -- checks legacyPackages)
35-
if [ -z "$result" ]; then
36-
echo "Error: github-matrix returned empty output" >&2
37-
exit 1
38-
fi
39-
echo matrix="$result" >> "$GITHUB_OUTPUT"
34+
nix run --accept-flake-config .\#github-matrix -- checks legacyPackages

nix/packages/github-matrix/default.nix

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,25 @@
55
}:
66
let
77
pname = "github-matrix";
8+
9+
github-action-utils = python3Packages.buildPythonPackage rec {
10+
pname = "github-action-utils";
11+
version = "1.1.0";
12+
pyproject = true;
13+
14+
src = python3Packages.fetchPypi {
15+
inherit pname version;
16+
sha256 = "0q9xrb4jcvbn6954lvpn85gva1yc885ykdqb2q2410cxp280v94a";
17+
};
18+
19+
build-system = with python3Packages; [ setuptools ];
20+
21+
meta = with lib; {
22+
description = "Collection of Python functions for GitHub Action Workflow Commands";
23+
homepage = "https://github.com/saadmk11/github-action-utils";
24+
license = licenses.mit;
25+
};
26+
};
827
in
928

1029
python3Packages.buildPythonApplication {
@@ -14,6 +33,11 @@ python3Packages.buildPythonApplication {
1433

1534
src = ./.;
1635

36+
propagatedBuildInputs = [
37+
github-action-utils
38+
python3Packages.result
39+
];
40+
1741
makeWrapperArgs = [ "--suffix PATH : ${lib.makeBinPath [ nix-eval-jobs ]}" ];
1842

1943
nativeCheckInputs = with python3Packages; [

nix/packages/github-matrix/github_matrix.py

Lines changed: 82 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env python3
22

33
import argparse
4-
from collections import defaultdict
4+
from collections import Counter, defaultdict
55
import graphlib
66
import json
77
import os
@@ -10,16 +10,19 @@
1010
from typing import (
1111
Any,
1212
Dict,
13-
Generator,
1413
List,
1514
Literal,
1615
NotRequired,
1716
Optional,
1817
Set,
18+
Tuple,
1919
TypedDict,
2020
get_args,
2121
)
2222

23+
from github_action_utils import debug, error, set_output, warning
24+
from result import Err, Ok, Result
25+
2326
System = Literal["x86_64-linux", "aarch64-linux", "aarch64-darwin"]
2427
RunnerType = Literal["ephemeral", "self-hosted"]
2528

@@ -103,53 +106,75 @@ def build_nix_eval_command(max_workers: int, flake_outputs: List[str]) -> List[s
103106

104107

105108
def parse_nix_eval_line(
106-
line: str, drv_paths: Set[str], errors: List[str]
107-
) -> Optional[NixEvalJobsOutput]:
108-
"""Parse a single line of nix-eval-jobs output"""
109+
line: str, drv_paths: Set[str]
110+
) -> Result[Optional[NixEvalJobsOutput], str]:
111+
"""Parse a single line of nix-eval-jobs output.
112+
113+
Returns:
114+
Ok(package_data) if successful (None for empty/duplicate lines)
115+
Err(error_message) if a nix evaluation error occurred
116+
"""
109117
if not line.strip():
110-
return None
118+
return Ok(None)
111119

112120
try:
113121
data: NixEvalJobsOutput = json.loads(line)
114122
if "error" in data:
115123
error_msg = (
116124
f"Error in nix-eval-jobs output for {data['attr']}: {data['error']}"
117125
)
118-
errors.append(error_msg)
119-
return None
126+
error(error_msg, title="Nix Evaluation Error")
127+
return Err(error_msg)
120128
if data["drvPath"] in drv_paths:
121-
return None
129+
return Ok(None)
122130
drv_paths.add(data["drvPath"])
123-
return data
124-
except json.JSONDecodeError:
125-
error_msg = f"Skipping invalid JSON line: {line}"
126-
print(error_msg, file=sys.stderr)
127-
errors.append(error_msg)
128-
return None
131+
return Ok(data)
132+
except json.JSONDecodeError as e:
133+
warning(f"Skipping invalid JSON line: {line}", title="JSON Parse Warning")
134+
return Ok(None)
129135

130136

131137
def run_nix_eval_jobs(
132-
cmd: List[str], errors: List[str]
133-
) -> Generator[NixEvalJobsOutput, None, None]:
134-
"""Run nix-eval-jobs and yield parsed package data."""
135-
print(f"Running command: {' '.join(cmd)}", file=sys.stderr)
136-
137-
with subprocess.Popen(
138-
cmd, stdout=subprocess.PIPE, stderr=None, text=True
139-
) as process:
140-
drv_paths: Set[str] = set()
141-
assert process.stdout is not None # for mypy
142-
for line in process.stdout:
143-
package = parse_nix_eval_line(line, drv_paths, errors)
144-
if package:
145-
yield package
146-
147-
process.wait()
148-
if process.returncode != 0:
149-
error_msg = "Error: nix-eval-jobs process failed with non-zero exit code"
150-
print(error_msg, file=sys.stderr)
151-
errors.append(error_msg)
152-
# Don't exit here - let main() handle it after reporting all errors
138+
cmd: List[str],
139+
) -> Tuple[List[NixEvalJobsOutput], List[str], bool]:
140+
"""Run nix-eval-jobs and return parsed package data, warnings, and error status.
141+
142+
Returns:
143+
Tuple of (packages, warnings_list, had_errors)
144+
"""
145+
debug(f"Running command: {' '.join(cmd)}")
146+
147+
process = subprocess.Popen(
148+
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
149+
)
150+
stdout_data, stderr_data = process.communicate()
151+
152+
# Parse stdout for packages
153+
packages: List[NixEvalJobsOutput] = []
154+
drv_paths: Set[str] = set()
155+
had_errors = False
156+
for line in stdout_data.splitlines():
157+
result = parse_nix_eval_line(line, drv_paths)
158+
if result.is_err():
159+
had_errors = True
160+
elif result.ok_value is not None:
161+
packages.append(result.ok_value)
162+
163+
# Parse stderr for warnings (lines starting with "warning:")
164+
warnings_list: List[str] = []
165+
for line in stderr_data.splitlines():
166+
line = line.strip()
167+
if line.startswith("warning:") or line.startswith("evaluation warning:"):
168+
# Remove "warning:" prefix for cleaner messages
169+
warnings_list.append(line[8:].strip())
170+
171+
if process.returncode != 0:
172+
error(
173+
"nix-eval-jobs process failed with non-zero exit code",
174+
title="Process Failure",
175+
)
176+
177+
return packages, warnings_list, had_errors
153178

154179

155180
def is_extension_pkg(pkg: NixEvalJobsOutput) -> bool:
@@ -235,9 +260,9 @@ def main() -> None:
235260

236261
cmd = build_nix_eval_command(max_workers, args.flake_outputs)
237262

238-
# Collect all evaluation errors
239-
errors: List[str] = []
240-
gh_action_packages = sort_pkgs_by_closures(list(run_nix_eval_jobs(cmd, errors)))
263+
# Run evaluation and collect packages and warnings
264+
packages, warnings_list, had_errors = run_nix_eval_jobs(cmd)
265+
gh_action_packages = sort_pkgs_by_closures(packages)
241266

242267
def clean_package_for_output(pkg: NixEvalJobsOutput) -> GitHubActionPackage:
243268
"""Convert nix-eval-jobs output to GitHub Actions matrix package"""
@@ -281,22 +306,24 @@ def clean_package_for_output(pkg: NixEvalJobsOutput) -> GitHubActionPackage:
281306
}
282307
]
283308
}
284-
print(
285-
f"debug: Generated GitHub Actions matrix: {json.dumps(gh_output, indent=2)}",
286-
file=sys.stderr,
287-
)
288-
print(json.dumps(gh_output))
289-
290-
# Check if any errors occurred during evaluation
291-
if errors:
292-
print("\n=== Evaluation Errors ===", file=sys.stderr)
293-
for i, error in enumerate(errors, 1):
294-
print(f"\nError {i}:", file=sys.stderr)
295-
print(error, file=sys.stderr)
296-
print(
297-
f"\n=== Total: {len(errors)} error(s) occurred during evaluation ===",
298-
file=sys.stderr,
299-
)
309+
310+
if warnings_list:
311+
warning_counts = Counter(warnings_list)
312+
for warn_msg, count in warning_counts.items():
313+
if count > 1:
314+
warning(
315+
f"{warn_msg} (occurred {count} times)",
316+
title="Nix Evaluation Warning",
317+
)
318+
else:
319+
warning(warn_msg, title="Nix Evaluation Warning")
320+
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))
324+
325+
# Exit with error code if any evaluation errors occurred
326+
if had_errors:
300327
sys.exit(1)
301328

302329

0 commit comments

Comments
 (0)