@@ -281,12 +281,64 @@ def write_csv(output: pathlib.Path, rows: List[Tuple]):
281281 for row in rows :
282282 writer .writerow (row )
283283
284- def sync_csv (rows : List [Tuple ], from_github : List [PaperInfo ]) -> List [Tuple ]:
284+ def create_github_issue (paper : PaperInfo , labels : List [str ]) -> None :
285+ """
286+ Create a new Github issue representing the given PaperInfo.
287+ """
288+ paper_name = paper .paper_name .replace ('``' , '`' ).replace ('\\ ' , '' )
289+
290+ create_cli = ['gh' , 'issue' , 'create' , '--repo' , 'llvm/llvm-project' ,
291+ '--title' , f'{ paper .paper_number } : { paper_name } ' ,
292+ '--body' , f'**Link:** https://wg21.link/{ paper .paper_number } ' ,
293+ '--project' , 'libc++ Standards Conformance' ,
294+ '--label' , 'libc++' ]
295+
296+ for label in labels :
297+ create_cli += ['--label' , label ]
298+
299+ print ("Do you want to create the following issue?" )
300+ print (create_cli )
301+ answer = input ("y/n: " )
302+ if answer == 'n' :
303+ print ("Not creating issue" )
304+ return
305+ elif answer != 'y' :
306+ print (f"Invalid answer { answer } , skipping" )
307+ return
308+
309+ print ("Creating issue" )
310+ issue_link = subprocess .check_output (create_cli ).decode ().strip ()
311+ print (f"Created tracking issue for { paper .paper_number } : { issue_link } " )
312+
313+ # Retrieve the "Github project item ID" by re-adding the issue to the project again,
314+ # even though we created it inside the project in the first place.
315+ item_add_cli = ['gh' , 'project' , 'item-add' , LIBCXX_CONFORMANCE_PROJECT , '--owner' , 'llvm' , '--url' , issue_link , '--format' , 'json' ]
316+ item = json .loads (subprocess .check_output (item_add_cli ).decode ().strip ())
317+
318+ # Then, adjust the 'Meeting Voted' field of that item.
319+ meeting_voted_cli = ['gh' , 'project' , 'item-edit' ,
320+ '--project-id' , 'PVT_kwDOAQWwKc4AlOgt' ,
321+ '--field-id' , 'PVTF_lADOAQWwKc4AlOgtzgdUEXI' , '--text' , paper .meeting ,
322+ '--id' , item ['id' ]]
323+ subprocess .check_call (meeting_voted_cli )
324+
325+ # And also adjust the 'Status' field of the item to 'To Do'.
326+ status_cli = ['gh' , 'project' , 'item-edit' ,
327+ '--project-id' , 'PVT_kwDOAQWwKc4AlOgt' ,
328+ '--field-id' , 'PVTSSF_lADOAQWwKc4AlOgtzgdUBak' , '--single-select-option-id' , 'f75ad846' ,
329+ '--id' , item ['id' ]]
330+ subprocess .check_call (status_cli )
331+
332+ def sync_csv (rows : List [Tuple ], from_github : List [PaperInfo ], create_new : bool , labels : List [str ] = None ) -> List [Tuple ]:
285333 """
286334 Given a list of CSV rows representing an existing status file and a list of PaperInfos representing
287335 up-to-date (but potentially incomplete) tracking information from Github, this function returns the
288336 new CSV rows synchronized with the up-to-date information.
289337
338+ If `create_new` is True and a paper from the CSV file is not tracked on Github yet, this also prompts
339+ to create a new issue on Github for tracking it. In that case the created issue is tagged with the
340+ provided labels.
341+
290342 Note that this only tracks changes from 'not implemented' issues to 'implemented'. If an up-to-date
291343 PaperInfo reports that a paper is not implemented but the existing CSV rows report it as implemented,
292344 it is an error (i.e. the result is not a CSV row where the paper is *not* implemented).
@@ -305,49 +357,82 @@ def sync_csv(rows: List[Tuple], from_github: List[PaperInfo]) -> List[Tuple]:
305357 # issue tracking it, which we validate below.
306358 tracking = [gh for gh in from_github if paper .paper_number == gh .paper_number ]
307359
308- # If there is no tracking issue for that row in the CSV, this is an error since we're
309- # missing a Github issue.
310- if len (tracking ) == 0 :
311- print (f"Can't find any Github issue for CSV row: { row } " )
360+ # If there's more than one tracking issue, something is weird.
361+ if len (tracking ) > 1 :
362+ print (f"Found a row with more than one tracking issue: { row } \n tracked by: { tracking } " )
312363 results .append (row )
313364 continue
314365
315- # If there's more than one tracking issue, something is weird too.
316- if len (tracking ) > 1 :
317- print (f"Found a row with more than one tracking issue: { row } \n tracked by: { tracking } " )
366+ # If there is no tracking issue for that row and we are creating new issues, do that.
367+ # Otherwise just log that we're missing an issue.
368+ if len (tracking ) == 0 :
369+ if create_new :
370+ assert labels is not None , "Missing labels when creating new Github issues"
371+ create_github_issue (paper , labels = labels )
372+ else :
373+ print (f"Can't find any Github issue for CSV row: { row } " )
318374 results .append (row )
319375 continue
320376
321377 results .append (merge (paper , tracking [0 ]).for_printing ())
322378
323379 return results
324380
325- CSV_FILES_TO_SYNC = [
326- 'Cxx17Issues.csv' ,
327- 'Cxx17Papers.csv' ,
328- 'Cxx20Issues.csv' ,
329- 'Cxx20Papers.csv' ,
330- 'Cxx23Issues.csv' ,
331- 'Cxx23Papers.csv' ,
332- 'Cxx2cIssues.csv' ,
333- 'Cxx2cPapers.csv' ,
334- ]
335-
336- def main ():
381+ CSV_FILES_TO_SYNC = {
382+ 'Cxx17Issues.csv' : ['c++17' , 'lwg-issue' ],
383+ 'Cxx17Papers.csv' : ['c++17' , 'wg21 paper' ],
384+ 'Cxx20Issues.csv' : ['c++20' , 'lwg-issue' ],
385+ 'Cxx20Papers.csv' : ['c++20' , 'wg21 paper' ],
386+ 'Cxx23Issues.csv' : ['c++23' , 'lwg-issue' ],
387+ 'Cxx23Papers.csv' : ['c++23' , 'wg21 paper' ],
388+ 'Cxx2cIssues.csv' : ['c++26' , 'lwg-issue' ],
389+ 'Cxx2cPapers.csv' : ['c++26' , 'wg21 paper' ],
390+ }
391+
392+ def main (argv ):
393+ import argparse
394+ parser = argparse .ArgumentParser (prog = 'synchronize-status-files' ,
395+ description = 'Synchronize the libc++ conformance status files with Github issues' )
396+ parser .add_argument ('--validate-only' , action = 'store_true' ,
397+ help = "Only perform the data validation of CSV files." )
398+ parser .add_argument ('--create-new' , action = 'store_true' ,
399+ help = "Create new Github issues for CSV rows that do not correspond to any existing Github issue." )
400+ parser .add_argument ('--load-github-from' , type = str ,
401+ help = "A json file to load the Github project information from instead of querying the API. This is useful for testing to avoid rate limiting." )
402+ args = parser .parse_args (argv )
403+
337404 libcxx_root = pathlib .Path (os .path .dirname (os .path .dirname (os .path .abspath (__file__ ))))
338405
339- # Extract the list of PaperInfos from issues we're tracking on Github.
340- print ("Loading all issues from Github" )
341- gh_command_line = ['gh' , 'project' , 'item-list' , LIBCXX_CONFORMANCE_PROJECT , '--owner' , 'llvm' , '--format' , 'json' , '--limit' , '9999999' ]
342- project_info = json .loads (subprocess .check_output (gh_command_line ))
406+ # Perform data validation for all the CSV files.
407+ print ("Performing data validation of the CSV files" )
408+ for filename in CSV_FILES_TO_SYNC :
409+ csv = load_csv (libcxx_root / 'docs' / 'Status' / filename )
410+ for row in csv [1 :]: # Skip the header
411+ if row [0 ] != "" : # Skip separator rows
412+ PaperInfo .from_csv_row (row )
413+
414+ if args .validate_only :
415+ return
416+
417+ # Load all the Github issues tracking papers from Github.
418+ if args .load_github_from :
419+ print (f"Loading all issues from { args .load_github_from } " )
420+ with open (args .load_github_from , 'r' ) as f :
421+ project_info = json .load (f )
422+ else :
423+ print ("Loading all issues from Github" )
424+ gh_command_line = ['gh' , 'project' , 'item-list' , LIBCXX_CONFORMANCE_PROJECT , '--owner' , 'llvm' , '--format' , 'json' , '--limit' , '9999999' ]
425+ project_info = json .loads (subprocess .check_output (gh_command_line ))
343426 from_github = [PaperInfo .from_github_issue (i ) for i in project_info ['items' ]]
344427
345- for filename in CSV_FILES_TO_SYNC :
428+ # Synchronize CSV files with the Github issues.
429+ for (filename , labels ) in CSV_FILES_TO_SYNC .items ():
346430 print (f"Synchronizing { filename } with Github issues" )
347431 file = libcxx_root / 'docs' / 'Status' / filename
348432 csv = load_csv (file )
349- synced = sync_csv (csv , from_github )
433+ synced = sync_csv (csv , from_github , create_new = args . create_new , labels = labels )
350434 write_csv (file , synced )
351435
352436if __name__ == '__main__' :
353- main ()
437+ import sys
438+ main (sys .argv [1 :])
0 commit comments