Skip to content

Commit 1b03747

Browse files
authored
[libc++] Augment Github - CSV synchronization script to auto-create new Github issues (#118139)
This makes it easier to create the Github issues and add them to the libc++ conformance project after a WG21 meeting.
1 parent 57452bb commit 1b03747

File tree

1 file changed

+112
-27
lines changed

1 file changed

+112
-27
lines changed

libcxx/utils/synchronize_csv_status_files.py

Lines changed: 112 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -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}\ntracked 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}\ntracked 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

352436
if __name__ == '__main__':
353-
main()
437+
import sys
438+
main(sys.argv[1:])

0 commit comments

Comments
 (0)