11#!/usr/bin/env python3
22
33import argparse
4+ from collections import defaultdict
5+ import graphlib
46import json
57import os
68import subprocess
@@ -48,6 +50,9 @@ class GitHubActionPackage(TypedDict):
4850 system : str
4951 already_cached : bool
5052 runs_on : RunsOnConfig
53+ drvPath : str
54+ neededSubstitutes : List [str ]
55+ neededBuilds : List [str ]
5156 postgresql_version : NotRequired [str ]
5257
5358
@@ -114,6 +119,9 @@ def parse_nix_eval_line(
114119 "system" : data ["system" ],
115120 "already_cached" : data .get ("cacheStatus" ) != "notBuilt" ,
116121 "runs_on" : runs_on_config ,
122+ "drvPath" : data ["drvPath" ],
123+ "neededSubstitutes" : data .get ("neededSubstitutes" , []),
124+ "neededBuilds" : data .get ("neededBuilds" , []),
117125 }
118126 except json .JSONDecodeError :
119127 print (f"Skipping invalid JSON line: { line } " , file = sys .stderr )
@@ -133,8 +141,7 @@ def run_nix_eval_jobs(
133141
134142 for line in process .stdout :
135143 package = parse_nix_eval_line (line , drv_paths , target )
136- if package and not package ["already_cached" ]:
137- print (f"Found package: { package ['attr' ]} " , file = sys .stderr )
144+ if package :
138145 yield package
139146
140147 if process .returncode and process .returncode != 0 :
@@ -149,6 +156,34 @@ def is_extension_pkg(pkg: GitHubActionPackage) -> bool:
149156 return attrs [- 2 ] == "exts"
150157
151158
159+ # thank you buildbot-nix https://github.com/nix-community/buildbot-nix/blob/985d069a2a45cf4a571a4346107671adc2bd2a16/buildbot_nix/buildbot_nix/build_trigger.py#L297
160+ def sort_pkgs_by_closures (jobs : list [GitHubActionPackage ]) -> list [GitHubActionPackage ]:
161+ sorted_jobs = []
162+
163+ # Prepare job dependencies
164+ job_set = {job ["drvPath" ] for job in jobs }
165+ job_closures = {
166+ k ["drvPath" ]: set (k ["neededSubstitutes" ])
167+ .union (set (k ["neededBuilds" ]))
168+ .intersection (job_set )
169+ .difference ({k ["drvPath" ]})
170+ for k in jobs
171+ }
172+
173+ sorter = graphlib .TopologicalSorter (job_closures )
174+
175+ for item in sorter .static_order ():
176+ i = 0
177+ while i < len (jobs ):
178+ if item == jobs [i ]["drvPath" ]:
179+ sorted_jobs .append (jobs [i ])
180+ del jobs [i ]
181+ else :
182+ i += 1
183+
184+ return sorted_jobs
185+
186+
152187def main () -> None :
153188 parser = argparse .ArgumentParser (
154189 description = "Generate GitHub Actions matrix for Nix builds"
@@ -168,23 +203,22 @@ def main() -> None:
168203
169204 cmd = build_nix_eval_command (max_workers , flake_output )
170205
171- gh_action_packages = list (run_nix_eval_jobs (cmd , flake_output ))
206+ gh_action_packages = sort_pkgs_by_closures (
207+ list (run_nix_eval_jobs (cmd , flake_output ))
208+ )
172209
173210 if args .target == "extensions" :
174211 # filter to only include extension packages and add postgresql_version field
175212 gh_action_packages = [
176213 {** pkg , "postgresql_version" : pkg ["attr" ].split ("." )[- 3 ]}
177214 for pkg in gh_action_packages
178- if is_extension_pkg (pkg )
215+ if is_extension_pkg (pkg ) and not pkg [ "already_cached" ]
179216 ]
180217
181218 # Group packages by system
182- grouped_by_system = {}
219+ grouped_by_system = defaultdict ( list )
183220 for pkg in gh_action_packages :
184- system = pkg ["system" ]
185- if system not in grouped_by_system :
186- grouped_by_system [system ] = []
187- grouped_by_system [system ].append (pkg )
221+ grouped_by_system [pkg ["system" ]].append (pkg )
188222
189223 # Create output with system-specific matrices
190224 gh_output = {}
0 commit comments