Skip to content

Commit ef1f793

Browse files
committed
feat(ci): do not build if already cached
1 parent db1e5e4 commit ef1f793

File tree

2 files changed

+153
-6
lines changed

2 files changed

+153
-6
lines changed

.github/workflows/nix-build.yml

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -33,29 +33,29 @@ jobs:
3333
name: Generate Nix Matrix
3434
run: |
3535
set -Eeu
36-
echo matrix="$(nix eval --json '.#githubActions.matrix')" >> "$GITHUB_OUTPUT"
36+
echo matrix="$(python scripts/github-matrix.py)" >> "$GITHUB_OUTPUT"
3737
3838
build-run-image:
3939
name: ${{ matrix.name }} (${{ matrix.system }})
4040
needs: nix-matrix
41-
runs-on:
42-
group: ${{ contains(matrix.os, 'blacksmith-32vcpu-ubuntu-2404') && '' || 'self-hosted-runners-nix' }}
43-
labels:
44-
- ${{ matrix.os }}
41+
runs-on: ${{ matrix.runs_on.group && matrix.runs_on || matrix.runs_on.labels }}
4542
strategy:
4643
fail-fast: false
4744
matrix: ${{fromJSON(needs.nix-matrix.outputs.matrix)}}
4845
steps:
4946
- name: Checkout Repo
5047
uses: actions/checkout@v4
48+
if: ${{ matrix.already_cached != 'true' }}
5149
- name: aws-oidc
5250
uses: aws-actions/[email protected]
51+
if: ${{ matrix.already_cached != 'true' }}
5352
with:
5453
aws-region: us-east-2
5554
role-to-assume: arn:aws:iam::279559813984:role/supabase-github-oidc-role # Shared Services
5655
role-session-name: gha-oidc-${{ github.run_id }}
5756
- name: aws-creds
5857
uses: aws-actions/[email protected]
58+
if: ${{ matrix.already_cached != 'true' }}
5959
with:
6060
disable-retry: true
6161
aws-region: us-east-2
@@ -65,6 +65,7 @@ jobs:
6565
role-skip-session-tagging: true
6666
role-duration-seconds: 900 # TODO: switch to 18000 (5 hours)
6767
- name: Write creds files
68+
if: ${{ matrix.already_cached != 'true' }}
6869
run: |
6970
umask 006
7071
cat > /etc/nix/aws/nix-aws-credentials <<EOF
@@ -73,8 +74,13 @@ jobs:
7374
aws_secret_access_key = ${AWS_SECRET_ACCESS_KEY}
7475
aws_session_token = ${AWS_SESSION_TOKEN}
7576
EOF
76-
- name: nix-fast-build
77+
- name: nix build
78+
if: ${{ matrix.already_cached != 'true' }}
7779
run: nix build -L
80+
- name: nix build
81+
if: ${{ matrix.already_cached == 'true' }}
82+
run: echo "{{ matrix.attr }} already cached, skipping build"
83+
7884
run-tests:
7985
needs: build-run-image
8086
if: ${{ success() }}

scripts/github-matrix.py

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
#!/usr/bin/env python3
2+
3+
import json
4+
import os
5+
import subprocess
6+
import sys
7+
from typing import Any, Dict, Generator, List, Literal, Optional, Set
8+
from typing_extensions import NotRequired, TypedDict
9+
10+
11+
class NixEvalJobsOutput(TypedDict):
12+
"""Raw output from nix-eval-jobs command."""
13+
14+
attr: str
15+
attrPath: List[str]
16+
cacheStatus: Literal["notBuilt", "cached", "local"]
17+
drvPath: str
18+
isCached: bool
19+
name: str
20+
system: str
21+
neededBuilds: NotRequired[List[Any]]
22+
neededSubstitutes: NotRequired[List[Any]]
23+
outputs: NotRequired[Dict[str, str]]
24+
25+
26+
class RunsOnConfig(TypedDict):
27+
"""GitHub Actions runs-on configuration."""
28+
29+
group: NotRequired[str]
30+
labels: List[str]
31+
32+
33+
class GitHubActionPackage(TypedDict):
34+
"""Processed package for GitHub Actions matrix."""
35+
36+
attr: str
37+
name: str
38+
system: str
39+
already_cached: bool
40+
runs_on: RunsOnConfig
41+
42+
43+
BUILD_RUNNER_MAP: Dict[str, RunsOnConfig] = {
44+
"aarch64-linux": {
45+
"group": "self-hosted-runners-nix",
46+
"labels": ["aarch64-linux"],
47+
},
48+
"aarch64-darwin": {
49+
"group": "self-hosted-runners-nix",
50+
"labels": ["aarch64-darwin"],
51+
},
52+
"x86_64-linux": {
53+
"labels": ["blacksmith-32vcpu-ubuntu-2404"],
54+
},
55+
}
56+
57+
58+
def get_worker_count() -> int:
59+
"""Get optimal worker count based on CPU cores."""
60+
try:
61+
return max(1, int(os.cpu_count() / 2))
62+
except (OSError, AttributeError):
63+
print(
64+
"Warning: Unable to get CPU count, using default max_workers=1",
65+
file=sys.stderr,
66+
)
67+
return 1
68+
69+
70+
def build_nix_eval_command(max_workers: int) -> List[str]:
71+
"""Build the nix-eval-jobs command with appropriate flags."""
72+
return [
73+
"nix-eval-jobs",
74+
"--flake",
75+
".#checks",
76+
"--check-cache-status",
77+
"--force-recurse",
78+
"--quiet",
79+
"--workers",
80+
str(max_workers),
81+
]
82+
83+
84+
def parse_nix_eval_line(
85+
line: str, drv_paths: Set[str]
86+
) -> Optional[GitHubActionPackage]:
87+
"""Parse a single line of nix-eval-jobs output"""
88+
if not line.strip():
89+
return None
90+
91+
try:
92+
data: NixEvalJobsOutput = json.loads(line)
93+
if data["drvPath"] in drv_paths:
94+
return None
95+
drv_paths.add(data["drvPath"])
96+
97+
runs_on_config = BUILD_RUNNER_MAP[data["system"]]
98+
99+
return {
100+
"attr": "checks." + data["attr"],
101+
"name": data["name"],
102+
"system": data["system"],
103+
"already_cached": data.get("cacheStatus") != "notBuilt",
104+
"runs_on": runs_on_config,
105+
}
106+
except json.JSONDecodeError:
107+
print(f"Skipping invalid JSON line: {line}", file=sys.stderr)
108+
return None
109+
110+
111+
def run_nix_eval_jobs(cmd: List[str]) -> Generator[GitHubActionPackage, None, None]:
112+
"""Run nix-eval-jobs and yield parsed package data."""
113+
print(f"Running command: {' '.join(cmd)}", file=sys.stderr)
114+
115+
with subprocess.Popen(
116+
cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True
117+
) as process:
118+
drv_paths = set()
119+
120+
for line in process.stdout:
121+
package = parse_nix_eval_line(line, drv_paths)
122+
if package:
123+
yield package
124+
125+
if process.returncode and process.returncode != 0:
126+
print("Error: Evaluation failed", file=sys.stderr)
127+
sys.stderr.write(process.stderr.read())
128+
sys.exit(process.returncode)
129+
130+
131+
def main() -> None:
132+
max_workers = get_worker_count()
133+
cmd = build_nix_eval_command(max_workers)
134+
135+
gh_action_packages = list(run_nix_eval_jobs(cmd))
136+
gh_output = {"include": gh_action_packages}
137+
print(json.dumps(gh_output))
138+
139+
140+
if __name__ == "__main__":
141+
main()

0 commit comments

Comments
 (0)