@@ -213,24 +213,46 @@ def tag_priority(tag):
213213 else :
214214 return - 1
215215
216- def find_previous_build_commit (build_tag , build ):
217- """Find the previous build in an higher priority koji tag and return its commit."""
216+ def find_previous_build_with_commit (build_tag , build ):
217+ """Find the previous build of the package in an higher priority koji tag and its source commit.
218+
219+ The builds whose source is unknown are skipped, so the most recent build providing a source commit
220+ is returned, as (build, commit). Return (None, None) when no such build exists.
221+ """
218222 tagged = KOJI .listTagged (build_tag , package = build ['package_name' ], inherit = True )
219223 tagged = sorted (tagged , key = lambda t : (tag_priority (t ['tag_name' ]), - t ['build_id' ]))
220224 build_tag_priority = tag_priority (build_tag )
221225 tagged = [
222226 t for t in tagged if tag_priority (t ['tag_name' ]) >= build_tag_priority and t ['build_id' ] < build ['build_id' ]
223227 ]
224-
225228 for tag in tagged :
226229 previous_build = get_koji_build (tag ['build_id' ])
227230 source = previous_build .get ('source' ) or MISSING_SOURCES .get (previous_build ['nvr' ])
228231 if not source :
229232 continue
230- return parse_source (source )[1 ]
233+ return previous_build , parse_source (source )[1 ]
231234
232- return None
235+ return None , None
233236
237+ def released_tags (build_tag ):
238+ """Return the tags holding the released builds of the version, e.g. v8.3-updates and v8.3-base."""
239+ version = build_tag .split ('-' )[0 ][1 :] # v8.3-candidates -> 8.3
240+ return [] if not version else [f'v{ version } -updates' , f'v{ version } -base' ]
241+
242+ def find_released_build (build_tag , package_name ):
243+ """Find the newest released build of the package in the updates and base tags, updates first.
244+
245+ Some versions may not have an updates or base tag yet, which koji reports as an error, so a
246+ missing tag is treated as an empty one.
247+ """
248+ for tag in released_tags (build_tag ):
249+ try :
250+ tagged = KOJI .listTagged (tag , package = package_name )
251+ except koji .GenericError :
252+ continue
253+ if tagged :
254+ return get_koji_build (max (tagged , key = lambda t : t ['build_id' ])['build_id' ])
255+ return None
234256
235257def find_commits (gh , repo , start_sha , end_sha ) -> list [Commit ]:
236258 """
@@ -386,6 +408,7 @@ def get_plane_issues_with_milestones(plane_token):
386408 '--package' , '-p' , dest = 'packages' , help = "The packages to include in the report" , action = 'append' , default = []
387409)
388410parser .add_argument ('--re-cache' , help = "Refresh the cache" , action = 'store_true' )
411+ parser .add_argument ('--json-output' , help = "Also write a machine readable report in json format to this path" )
389412args = parser .parse_args ()
390413
391414CACHE = diskcache .Cache (args .cache )
@@ -420,6 +443,14 @@ def get_plane_issues_with_milestones(plane_token):
420443with urlopen ('https://github.com/xcp-ng/xcp/raw/refs/heads/master/scripts/rpm_owners/packages.json' ) as f :
421444 PACKAGES = json .load (f )
422445
446+ report_data = {
447+ 'generated_at' : started_at .isoformat (),
448+ 'generated_info' : args .generated_info ,
449+ 'warnings' : {'plane' : not issues , 'github' : not GITHUB },
450+ 'error' : None ,
451+ 'tags' : [],
452+ }
453+
423454with io .StringIO () as out :
424455 print_header (out )
425456 if not issues :
@@ -433,6 +464,8 @@ def get_plane_issues_with_milestones(plane_token):
433464 KOJI = koji .ClientSession ('https://kojihub.xcp-ng.org' , config )
434465 KOJI .ssl_login (config ['cert' ], None , config ['serverca' ])
435466 for tag in tags :
467+ tag_data = {'tag' : tag , 'builds' : []}
468+ report_data ['tags' ].append (tag_data )
436469 tag_history = dict (
437470 (tl ['build_id' ], tl ['create_ts' ])
438471 for tl in KOJI .queryHistory (tag = tag , active = True )['tag_listing' ]
@@ -445,7 +478,8 @@ def get_plane_issues_with_milestones(plane_token):
445478 build = get_koji_build (tagged ['build_id' ])
446479 prs : list [PullRequest ] = []
447480 maintained_by = None
448- previous_build_sha = find_previous_build_commit (tag , build )
481+ (previous_build , previous_build_sha ) = find_previous_build_with_commit (tag , build )
482+ released_build = find_released_build (tag , tagged ['package_name' ])
449483 if build ['source' ] is not None :
450484 (repo , sha ) = parse_source (build ['source' ])
451485 prs = find_pull_requests (repo , sha , previous_build_sha )
@@ -455,12 +489,34 @@ def get_plane_issues_with_milestones(plane_token):
455489 print_table_line (
456490 temp_out , tagged ['nvr' ], build_url , build_issues , tagged ['owner_name' ], prs , maintained_by
457491 )
492+ tag_data ['builds' ].append ({
493+ 'nvr' : tagged ['nvr' ],
494+ 'previous_nvr' : released_build ['nvr' ] if released_build is not None else None ,
495+ 'package' : tagged ['package_name' ],
496+ 'url' : build_url ,
497+ 'built_by' : tagged ['owner_name' ],
498+ 'maintained_by' : maintained_by ,
499+ 'issues' : [{
500+ 'sequence_id' : i ['sequence_id' ],
501+ 'url' : f'https://project.vates.tech/vates-global/browse/XCPNG-{ i ["sequence_id" ]} /' ,
502+ 'milestones' : sorted (set (i ['milestones' ])),
503+ } for i in build_issues ],
504+ 'pull_requests' : [{
505+ 'number' : pr .number ,
506+ 'title' : pr .title ,
507+ 'url' : pr .html_url ,
508+ 'linked' : issues_have_link (build_issues , pr .html_url ),
509+ } for pr in prs ],
510+ 'build_linked' : issues_have_link (build_issues , build_url ),
511+ })
458512 print_table_footer (temp_out )
459513 out .write (temp_out .getvalue ())
460514 except koji .GenericError :
515+ report_data ['error' ] = 'koji'
461516 print_koji_error (out )
462517 raise
463518 except Exception :
519+ report_data ['error' ] = 'unknown'
464520 print_generic_error (out )
465521 raise
466522 finally :
@@ -469,3 +525,7 @@ def get_plane_issues_with_milestones(plane_token):
469525 # write the actual output at once, in order to avoid a blank page during the processing
470526 with open (args .output , 'w' ) as f :
471527 f .write (out .getvalue ())
528+
529+ if args .json_output :
530+ with open (args .json_output , 'w' ) as f :
531+ json .dump (report_data , f , indent = 2 )
0 commit comments