2626 *source* and a stub *build* section remain.
2727* Automatically invokes ``rattler-build build`` if *--dry* is **not**
2828 given.
29+
30+ Modification summary
31+ --------------------
32+ * Each recipe is built individually (not batch)
33+ * All outputs collected; failures reported with summary and details
34+ * No early stopping; CI-friendly non-zero exit if any failures
2935"""
3036
3137from __future__ import annotations
3844from typing import Any , Dict , List , Union
3945import yaml
4046
47+ # Make console writes UTF-8 and never crash on unknown glyphs (Windows-safe)
48+ try :
49+ sys .stdout .reconfigure (encoding = "utf-8" , errors = "replace" )
50+ sys .stderr .reconfigure (encoding = "utf-8" , errors = "replace" )
51+ except Exception :
52+ pass
4153
4254ROOT_DIR = Path .cwd ()
4355RECIPES_DIR = ROOT_DIR / "recipes"
@@ -130,15 +142,68 @@ def prepare_patch_recipes() -> List[Path]:
130142 return recreated
131143
132144
133- def run_rattler_build () -> None :
134- cmd = [
135- "rattler-build" ,
136- "build" ,
137- "--recipe-dir" ,
138- str (PATCH_RECIPES_DIR )
139- ]
140- print ("\n Running:" , " " .join (cmd ), "\n " , flush = True )
141- subprocess .run (cmd , check = True )
145+ def run_rattler_build_individually (recipes : List [Path ]) -> None :
146+ results = []
147+ for recipe_file in recipes :
148+ cmd = [
149+ "rattler-build" ,
150+ "build" ,
151+ "--recipe-dir" ,
152+ str (recipe_file .parent ),
153+ ]
154+ print ("\n Running:" , " " .join (cmd ), "\n " , flush = True )
155+ try :
156+ proc = subprocess .run (cmd , text = True , capture_output = True , errors = "replace" , encoding = "utf-8" )
157+ success = proc .returncode == 0
158+ results .append (
159+ {
160+ "recipe" : str (recipe_file .parent .relative_to (PATCH_RECIPES_DIR )),
161+ "ok" : success ,
162+ "stdout" : proc .stdout ,
163+ "stderr" : proc .stderr ,
164+ "rc" : proc .returncode ,
165+ }
166+ )
167+ print (" ->" , "OK" if success else f"FAIL (rc={ proc .returncode } )" , flush = True )
168+ except Exception as e :
169+ results .append (
170+ {
171+ "recipe" : str (recipe_file .parent .relative_to (PATCH_RECIPES_DIR )),
172+ "ok" : False ,
173+ "stdout" : "" ,
174+ "stderr" : str (e ),
175+ "rc" : - 1 ,
176+ }
177+ )
178+ print (" -> EXCEPTION:" , e , flush = True )
179+
180+ # Summary
181+ failed = [r for r in results if not r ["ok" ]]
182+ print ("\n ================ Patch Application Summary ================\n " )
183+ print (f"Total recipes tested: { len (results )} " )
184+ print (f"Passed: { len (results ) - len (failed )} " )
185+ print (f"Failed: { len (failed )} " )
186+
187+ if not failed :
188+ print ("\n All patches applied cleanly.\n " )
189+ return
190+
191+ print ("\n ---------------- Failures (Summary) ----------------" )
192+ for r in failed :
193+ print (f"- { r ['recipe' ]} (rc={ r ['rc' ]} )" )
194+
195+ print ("\n ---------------- Failures (Details) ----------------" )
196+ for r in failed :
197+ print (f"\n ### { r ['recipe' ]} (rc={ r ['rc' ]} )" )
198+ if r ["stdout" ]:
199+ print ("\n [stdout]" )
200+ print (r ["stdout" ].rstrip ())
201+ if r ["stderr" ]:
202+ print ("\n [stderr]" )
203+ print (r ["stderr" ].rstrip ())
204+ print ("\n ----------------------------------------------------\n " )
205+
206+ sys .exit (2 if failed else 0 )
142207
143208
144209def main () -> None :
@@ -165,11 +230,10 @@ def main() -> None:
165230 print (f"Prepared { len (recreated )} minimal recipe(s) in { PATCH_RECIPES_DIR } /" )
166231
167232 if not args .dry :
168- run_rattler_build ( )
233+ run_rattler_build_individually ( recreated )
169234 else :
170235 print ("--dry given – rattler-build not executed." )
171236
172237
173238if __name__ == "__main__" :
174239 main ()
175-
0 commit comments