#!/usr/bin/env python3 """Update Options.td for the flags changes in https://reviews.llvm.org/Dxyz This script translates Options.td from using Flags to control option visibility to using Vis instead. It is meant to be idempotent and usable to help update downstream forks if they have their own changes to Options.td. Usage: ```sh % update_options_td_flags.py path/to/Options.td > Options.td.new % mv Options.td.new path/to/Options.td ``` This script will be removed after the next LLVM release. """ import argparse import re import shutil import sys import tempfile def rewrite_option_flags(input_file, output_file): for src_line in input_file: for dst_line in process_line(src_line): output_file.write(dst_line) def process_line(line): # We only deal with one thing per line. If multiple things can be # on the same line (like NegFlag and PosFlag), please preprocess # that first. m = re.search(r'((NegFlag|PosFlag)<[A-Za-z]+, |BothFlags<)' r'\[([A-Za-z0-9, ]+)\](, \[ClangOption\]|(?=>))', line) if m: return process_boolflags(m.group(3), line[:m.end(1)], line[m.end():]) m = re.search(r'\bFlags<\[([A-Za-z0-9, ]*)\]>', line) if m: return process_flags(m.group(1), line[:m.start()], line[m.end():]) m = re.search(r'let Flags = \[([A-Za-z0-9, ]*)\]', line) if m: return process_letflags(m.group(1), line[:m.start(1)], line[m.end():]) return [line] def process_boolflags(flag_group, prefix, suffix): flags = [f.strip() for f in flag_group.split(',')] if flag_group else [] if not flags: return f'{prefix}[], [ClangOption]{suffix}' flags_to_keep, vis_mods = translate_flags(flags) flag_text = f'[{", ".join(flags_to_keep)}]' vis_text = f'[{", ".join(vis_mods)}]' new_text = ', '.join([flag_text, vis_text]) if prefix.startswith('Both'): indent = ' ' * len(prefix) else: indent = ' ' * (len(prefix) - len(prefix.lstrip()) + len('XyzFlag<')) return get_edited_lines(prefix, new_text, suffix, indent=indent) def process_flags(flag_group, prefix, suffix): flags = [f.strip() for f in flag_group.split(',')] flags_to_keep, vis_mods = translate_flags(flags) flag_text = '' vis_text = '' if flags_to_keep: flag_text = f'Flags<[{", ".join(flags_to_keep)}]>' if vis_mods: flag_text += ', ' if vis_mods: vis_text = f'Visibility<[{", ".join(vis_mods)}]>' return get_edited_lines(prefix, flag_text, vis_text, suffix) def process_letflags(flag_group, prefix, suffix): is_end_comment = prefix.startswith('} //') if not is_end_comment and not prefix.startswith('let'): raise AssertionError(f'Unusual let block: {prefix}') flags = [f.strip() for f in flag_group.split(',')] flags_to_keep, vis_mods = translate_flags(flags) lines = [] if flags_to_keep: lines += [f'let Flags = [{", ".join(flags_to_keep)}]'] if vis_mods: lines += [f'let Visibility = [{", ".join(vis_mods)}]'] if is_end_comment: lines = list(reversed([f'}} // {l}\n' for l in lines])) else: lines = [f'{l} in {{\n' for l in lines] return lines def get_edited_lines(prefix, *fragments, indent=' '): out_lines = [] current = prefix for fragment in fragments: if fragment and len(current) + len(fragment) > 80: # Make a minimal attempt at reasonable line lengths if fragment.startswith(',') or fragment.startswith(';'): # Avoid wrapping the , or ; to the new line current += fragment[0] fragment = fragment[1:].lstrip() out_lines += [current.rstrip() + '\n'] current = max(' ' * (len(current) - len(current.lstrip())), indent) current += fragment if current.strip(): out_lines += [current] return out_lines def translate_flags(flags): driver_flags = [ 'HelpHidden', 'RenderAsInput', 'RenderJoined', 'RenderSeparate', ] custom_flags = [ 'NoXarchOption', 'LinkerInput', 'NoArgumentUnused', 'Unsupported', 'LinkOption', 'Ignored', 'TargetSpecific', ] flag_to_vis = { 'CoreOption': ['ClangOption', 'CLOption', 'DXCOption'], 'CLOption': ['CLOption'], 'CC1Option': ['ClangOption', 'CC1Option'], 'CC1AsOption': ['ClangOption', 'CC1AsOption'], 'FlangOption': ['ClangOption', 'FlangOption'], 'FC1Option': ['ClangOption', 'FC1Option'], 'DXCOption': ['DXCOption'], 'CLDXCOption': ['CLOption', 'DXCOption'], } new_flags = [] vis_mods = [] has_no_driver = False has_flang_only = False for flag in flags: if flag in driver_flags or flag in custom_flags: new_flags += [flag] elif flag in flag_to_vis: vis_mods += flag_to_vis[flag] elif flag == 'NoDriverOption': has_no_driver = True elif flag == 'FlangOnlyOption': has_flang_only = True else: raise AssertionError(f'Unknown flag: {flag}') new_vis_mods = [] for vis in vis_mods: if vis in new_vis_mods: continue if has_no_driver and vis == 'ClangOption': continue if has_flang_only and vis == 'ClangOption': continue new_vis_mods += [vis] return new_flags, new_vis_mods def main(): parser = argparse.ArgumentParser() parser.add_argument('src', nargs='?', default='-', type=argparse.FileType('r', encoding='UTF-8')) parser.add_argument('-o', dest='dst', default='-', type=argparse.FileType('w', encoding='UTF-8')) args = parser.parse_args() rewrite_option_flags(args.src, args.dst) if __name__ == '__main__': main()