blob: 382893dd9e0fef1e5b07ecef5b55147b5462a377 [file] [log] [blame]
Gurchetan Singh876877a2023-08-07 15:15:58 -07001#!/usr/bin/env python3
2"""
3Check for and replace aliases with their new names from vk.xml
4"""
5
6import argparse
7import pathlib
8import subprocess
9import sys
10import xml.etree.ElementTree as et
11
12THIS_FILE = pathlib.Path(__file__)
13CWD = pathlib.Path.cwd()
14
15VK_XML = THIS_FILE.parent / 'vk.xml'
16EXCLUDE_PATHS = [
17 VK_XML.relative_to(CWD).as_posix(),
18
19 # These files come from other repos, there's no point checking and
20 # fixing them here as that would be overwritten in the next sync.
21 'src/amd/vulkan/radix_sort/',
22 'src/virtio/venus-protocol/',
23]
24
25
26def get_aliases(xml_file: pathlib.Path):
27 """
28 Get all the aliases defined in vk.xml
29 """
30 xml = et.parse(xml_file)
31
32 for node in ([]
33 + xml.findall('.//enum[@alias]')
34 + xml.findall('.//type[@alias]')
35 + xml.findall('.//command[@alias]')
36 ):
37 yield node.attrib['name'], node.attrib['alias']
38
39
40def remove_prefix(string: str, prefix: str):
41 """
42 Remove prefix if string starts with it, and return the full string
43 otherwise.
44 """
45 if not string.startswith(prefix):
46 return string
47 return string[len(prefix):]
48
49
50# Function from https://stackoverflow.com/a/312464
51def chunks(lst: list, n: int):
52 """
53 Yield successive n-sized chunks from lst.
54 """
55 for i in range(0, len(lst), n):
56 yield lst[i:i + n]
57
58
59def main(check_only: bool):
60 """
61 Entrypoint; perform the search for all the aliases, and if `check_only`
62 is not True, replace them.
63 """
64 def prepare_identifier(identifier: str) -> str:
65 # vk_find_struct() prepends `VK_STRUCTURE_TYPE_`, so that prefix
66 # might not appear in the code
67 identifier = remove_prefix(identifier, 'VK_STRUCTURE_TYPE_')
68 return identifier
69
70 aliases = {}
71 for old_name, alias_for in get_aliases(VK_XML):
72 old_name = prepare_identifier(old_name)
73 alias_for = prepare_identifier(alias_for)
74 aliases[old_name] = alias_for
75
76 print(f'Found {len(aliases)} aliases in {VK_XML.name}')
77
78 # Some aliases have aliases
79 recursion_needs_checking = True
80 while recursion_needs_checking:
81 recursion_needs_checking = False
82 for old, new in aliases.items():
83 if new in aliases:
84 aliases[old] = aliases[new]
85 recursion_needs_checking = True
86
87 # Doing the whole search in a single command breaks grep, so only
88 # look for 500 aliases at a time. Searching them one at a time would
89 # be extremely slow.
90 files_with_aliases = set()
91 for aliases_chunk in chunks([*aliases], 500):
92 search_output = subprocess.check_output([
93 'git',
94 'grep',
95 '-rlP',
96 '|'.join(aliases_chunk),
97 'src/'
98 ], stderr=subprocess.DEVNULL).decode()
99 files_with_aliases.update(search_output.splitlines())
100
101 def file_matches_path(file: str, path: str) -> bool:
102 # if path is a folder; match any file within
103 if path.endswith('/') and file.startswith(path):
104 return True
105 return file == path
106
107 for excluded_path in EXCLUDE_PATHS:
108 files_with_aliases = {
109 file for file in files_with_aliases
110 if not file_matches_path(file, excluded_path)
111 }
112
113 if not files_with_aliases:
114 print('No alias found in any file.')
115 sys.exit(0)
116
117 print(f'{len(files_with_aliases)} files contain aliases:')
118 print('\n'.join(f'- {file}' for file in files_with_aliases))
119
120 if check_only:
121 print('You can automatically fix this by running '
122 f'`{THIS_FILE.relative_to(CWD)}`.')
123 sys.exit(1)
124
125 command = [
126 'sed',
127 '-i',
128 ";".join([f's/{old}/{new}/g' for old, new in aliases.items()]),
129 ]
130 command += files_with_aliases
131 subprocess.check_call(command, stderr=subprocess.DEVNULL)
132 print('All aliases have been replaced')
133
134
135if __name__ == '__main__':
136 parser = argparse.ArgumentParser()
137 parser.add_argument('--check-only',
138 action='store_true',
139 help='Replace aliases found')
140 args = parser.parse_args()
141 main(**vars(args))