[clang-format] Fix a bug in aligning comments above PPDirective (#72791)
[llvm-project.git] / clang / utils / update_options_td_flags.py
blob9271b453c3fd6fba95857d694fbae621d4d0c39c
1 #!/usr/bin/env python3
2 """Update Options.td for the flags changes in https://reviews.llvm.org/Dxyz
4 This script translates Options.td from using Flags to control option visibility
5 to using Vis instead. It is meant to be idempotent and usable to help update
6 downstream forks if they have their own changes to Options.td.
8 Usage:
9 ```sh
10 % update_options_td_flags.py path/to/Options.td > Options.td.new
11 % mv Options.td.new path/to/Options.td
12 ```
14 This script will be removed after the next LLVM release.
15 """
17 import argparse
18 import re
19 import shutil
20 import sys
21 import tempfile
23 def rewrite_option_flags(input_file, output_file):
24 for src_line in input_file:
25 for dst_line in process_line(src_line):
26 output_file.write(dst_line)
28 def process_line(line):
29 # We only deal with one thing per line. If multiple things can be
30 # on the same line (like NegFlag and PosFlag), please preprocess
31 # that first.
32 m = re.search(r'((NegFlag|PosFlag)<[A-Za-z]+, |BothFlags<)'
33 r'\[([A-Za-z0-9, ]+)\](, \[ClangOption\]|(?=>))', line)
34 if m:
35 return process_boolflags(m.group(3), line[:m.end(1)], line[m.end():])
36 m = re.search(r'\bFlags<\[([A-Za-z0-9, ]*)\]>', line)
37 if m:
38 return process_flags(m.group(1), line[:m.start()], line[m.end():])
39 m = re.search(r'let Flags = \[([A-Za-z0-9, ]*)\]', line)
40 if m:
41 return process_letflags(m.group(1), line[:m.start(1)], line[m.end():])
43 return [line]
45 def process_boolflags(flag_group, prefix, suffix):
46 flags = [f.strip() for f in flag_group.split(',')] if flag_group else []
47 if not flags:
48 return f'{prefix}[], [ClangOption]{suffix}'
50 flags_to_keep, vis_mods = translate_flags(flags)
51 flag_text = f'[{", ".join(flags_to_keep)}]'
52 vis_text = f'[{", ".join(vis_mods)}]'
53 new_text = ', '.join([flag_text, vis_text])
55 if prefix.startswith('Both'):
56 indent = ' ' * len(prefix)
57 else:
58 indent = ' ' * (len(prefix) - len(prefix.lstrip()) + len('XyzFlag<'))
60 return get_edited_lines(prefix, new_text, suffix, indent=indent)
62 def process_flags(flag_group, prefix, suffix):
63 flags = [f.strip() for f in flag_group.split(',')]
65 flags_to_keep, vis_mods = translate_flags(flags)
67 flag_text = ''
68 vis_text = ''
69 if flags_to_keep:
70 flag_text = f'Flags<[{", ".join(flags_to_keep)}]>'
71 if vis_mods:
72 flag_text += ', '
73 if vis_mods:
74 vis_text = f'Visibility<[{", ".join(vis_mods)}]>'
76 return get_edited_lines(prefix, flag_text, vis_text, suffix)
78 def process_letflags(flag_group, prefix, suffix):
79 is_end_comment = prefix.startswith('} //')
80 if not is_end_comment and not prefix.startswith('let'):
81 raise AssertionError(f'Unusual let block: {prefix}')
83 flags = [f.strip() for f in flag_group.split(',')]
85 flags_to_keep, vis_mods = translate_flags(flags)
87 lines = []
88 if flags_to_keep:
89 lines += [f'let Flags = [{", ".join(flags_to_keep)}]']
90 if vis_mods:
91 lines += [f'let Visibility = [{", ".join(vis_mods)}]']
93 if is_end_comment:
94 lines = list(reversed([f'}} // {l}\n' for l in lines]))
95 else:
96 lines = [f'{l} in {{\n' for l in lines]
97 return lines
99 def get_edited_lines(prefix, *fragments, indent=' '):
100 out_lines = []
101 current = prefix
102 for fragment in fragments:
103 if fragment and len(current) + len(fragment) > 80:
104 # Make a minimal attempt at reasonable line lengths
105 if fragment.startswith(',') or fragment.startswith(';'):
106 # Avoid wrapping the , or ; to the new line
107 current += fragment[0]
108 fragment = fragment[1:].lstrip()
109 out_lines += [current.rstrip() + '\n']
110 current = max(' ' * (len(current) - len(current.lstrip())), indent)
111 current += fragment
113 if current.strip():
114 out_lines += [current]
115 return out_lines
117 def translate_flags(flags):
118 driver_flags = [
119 'HelpHidden',
120 'RenderAsInput',
121 'RenderJoined',
122 'RenderSeparate',
124 custom_flags = [
125 'NoXarchOption',
126 'LinkerInput',
127 'NoArgumentUnused',
128 'Unsupported',
129 'LinkOption',
130 'Ignored',
131 'TargetSpecific',
133 flag_to_vis = {
134 'CoreOption': ['ClangOption', 'CLOption', 'DXCOption'],
135 'CLOption': ['CLOption'],
136 'CC1Option': ['ClangOption', 'CC1Option'],
137 'CC1AsOption': ['ClangOption', 'CC1AsOption'],
138 'FlangOption': ['ClangOption', 'FlangOption'],
139 'FC1Option': ['ClangOption', 'FC1Option'],
140 'DXCOption': ['DXCOption'],
141 'CLDXCOption': ['CLOption', 'DXCOption'],
143 new_flags = []
144 vis_mods = []
145 has_no_driver = False
146 has_flang_only = False
147 for flag in flags:
148 if flag in driver_flags or flag in custom_flags:
149 new_flags += [flag]
150 elif flag in flag_to_vis:
151 vis_mods += flag_to_vis[flag]
152 elif flag == 'NoDriverOption':
153 has_no_driver = True
154 elif flag == 'FlangOnlyOption':
155 has_flang_only = True
156 else:
157 raise AssertionError(f'Unknown flag: {flag}')
159 new_vis_mods = []
160 for vis in vis_mods:
161 if vis in new_vis_mods:
162 continue
163 if has_no_driver and vis == 'ClangOption':
164 continue
165 if has_flang_only and vis == 'ClangOption':
166 continue
167 new_vis_mods += [vis]
169 return new_flags, new_vis_mods
171 def main():
172 parser = argparse.ArgumentParser()
173 parser.add_argument('src', nargs='?', default='-',
174 type=argparse.FileType('r', encoding='UTF-8'))
175 parser.add_argument('-o', dest='dst', default='-',
176 type=argparse.FileType('w', encoding='UTF-8'))
178 args = parser.parse_args()
179 rewrite_option_flags(args.src, args.dst)
181 if __name__ == '__main__':
182 main()