xref: /aosp_15_r20/external/mesa3d/src/vulkan/registry/update-aliases.py (revision 6104692788411f58d303aa86923a9ff6ecaded22)
1#!/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        # Some renames only apply to some APIs
38        if 'api' in node.attrib and 'vulkan' not in node.attrib['api'].split(','):
39            continue
40
41        yield node.attrib['name'], node.attrib['alias']
42
43
44def remove_prefix(string: str, prefix: str):
45    """
46    Remove prefix if string starts with it, and return the full string
47    otherwise.
48    """
49    if not string.startswith(prefix):
50        return string
51    return string[len(prefix):]
52
53
54# Function from https://stackoverflow.com/a/312464
55def chunks(lst: list, n: int):
56    """
57    Yield successive n-sized chunks from lst.
58    """
59    for i in range(0, len(lst), n):
60        yield lst[i:i + n]
61
62
63def main(paths: list[str]):
64    """
65    Entrypoint; perform the search for all the aliases and replace them.
66    """
67    def prepare_identifier(identifier: str) -> str:
68        prefixes_seen = []
69        for prefix in [
70            # Various macros prepend these, so they will not appear in the code using them.
71            # List generated using this command:
72            #   $ prefixes=$(git grep -woiE 'VK_\w+_' -- src/ ':!src/vulkan/registry/' | cut -d: -f2 | sort -u)
73            #   $ for prefix in $prefixes; do grep -q $prefix src/vulkan/registry/vk.xml && echo "'$prefix',"; done
74            # (the second part eliminates prefixes used only in mesa code and not upstream)
75            'VK_BLEND_FACTOR_',
76            'VK_BLEND_OP_',
77            'VK_BORDER_COLOR_',
78            'VK_COMMAND_BUFFER_RESET_',
79            'VK_COMMAND_POOL_RESET_',
80            'VK_COMPARE_OP_',
81            'VK_COMPONENT_SWIZZLE_',
82            'VK_DESCRIPTOR_TYPE_',
83            'VK_DRIVER_ID_',
84            'VK_DYNAMIC_STATE_',
85            'VK_FORMAT_',
86            'VK_IMAGE_ASPECT_MEMORY_PLANE_',
87            'VK_IMAGE_ASPECT_PLANE_',
88            'VK_IMAGE_USAGE_',
89            'VK_NV_',
90            'VK_PERFORMANCE_COUNTER_UNIT_',
91            'VK_PIPELINE_BIND_POINT_',
92            'VK_SAMPLER_ADDRESS_MODE_',
93            'VK_SHADER_STAGE_TESSELLATION_',
94            'VK_SHADER_STAGE_',
95            'VK_STENCIL_OP_',
96            'VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_',
97            'VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_',
98            'VK_STRUCTURE_TYPE_',
99            'VK_USE_PLATFORM_',
100            'VK_VERSION_',
101
102            # Many places use the identifier without the `vk` prefix
103            # (eg. with the driver name as a prefix instead)
104            'VK_',
105            'Vk',
106            'vk',
107        ]:
108            # The order matters!  A shorter substring will match before a longer
109            # one, hiding its matches.
110            for prefix_seen in prefixes_seen:
111                assert not prefix.startswith(prefix_seen), f'{prefix_seen} must come before {prefix}'
112            prefixes_seen.append(prefix)
113
114            identifier = remove_prefix(identifier, prefix)
115
116        return identifier
117
118    aliases = {}
119    for old_name, alias_for in get_aliases(VK_XML):
120        old_name = prepare_identifier(old_name)
121        alias_for = prepare_identifier(alias_for)
122        aliases[old_name] = alias_for
123
124    print(f'Found {len(aliases)} aliases in {VK_XML.name}')
125
126    # Some aliases have aliases
127    recursion_needs_checking = True
128    while recursion_needs_checking:
129        recursion_needs_checking = False
130        for old, new in aliases.items():
131            if new in aliases:
132                aliases[old] = aliases[new]
133                recursion_needs_checking = True
134
135    # Doing the whole search in a single command breaks grep, so only
136    # look for 500 aliases at a time. Searching them one at a time would
137    # be extremely slow.
138    files_with_aliases = set()
139    for aliases_chunk in chunks([*aliases], 500):
140        grep_cmd = [
141            'git',
142            'grep',
143            '-rlP',
144            '|'.join(aliases_chunk),
145        ] + paths
146        search_output = subprocess.run(
147            grep_cmd,
148            check=False,
149            stdout=subprocess.PIPE,
150            stderr=subprocess.DEVNULL,
151        ).stdout.decode()
152        files_with_aliases.update(search_output.splitlines())
153
154
155    def file_matches_path(file: str, path: str) -> bool:
156        # if path is a folder; match any file within
157        if path.endswith('/') and file.startswith(path):
158            return True
159        return file == path
160
161    for excluded_path in EXCLUDE_PATHS:
162        files_with_aliases = {
163            file for file in files_with_aliases
164            if not file_matches_path(file, excluded_path)
165        }
166
167    if not files_with_aliases:
168        print('No alias found in any file.')
169        sys.exit(0)
170
171    print(f'{len(files_with_aliases)} files contain aliases:')
172    print('\n'.join(f'- {file}' for file in sorted(files_with_aliases)))
173
174    command = [
175        'sed',
176        '-i',
177        ";".join([f's/{old}/{new}/g' for old, new in aliases.items()]),
178    ]
179    command += files_with_aliases
180    subprocess.check_call(command, stderr=subprocess.DEVNULL)
181    print('All aliases have been replaced')
182
183
184if __name__ == '__main__':
185    parser = argparse.ArgumentParser()
186    parser.add_argument('paths',
187                        nargs=argparse.ZERO_OR_MORE,
188                        default=['src/'],
189                        help='Limit script to these paths (default: `src/`)')
190    args = parser.parse_args()
191    main(**vars(args))
192