1#!/usr/bin/env python3 2# SPDX-License-Identifier: BSD-3-Clause 3# Copyright(c) 2019 Intel Corporation 4 5""" 6A Python program that updates and merges all available stable ABI versions into 7one ABI version, while leaving experimental ABI exactly as it is. The intended 8ABI version is supplied via command-line parameter. This script is to be called 9from the devtools/update-abi.sh utility. 10""" 11 12import argparse 13import sys 14import re 15 16 17def __parse_map_file(f_in): 18 # match function name, followed by semicolon, followed by EOL or comments, 19 # optionally with whitespace in between each item 20 func_line_regex = re.compile(r"\s*" 21 r"(?P<line>" 22 r"(?P<func>[a-zA-Z_0-9]+)" 23 r"\s*" 24 r";" 25 r"\s*" 26 r"(?P<comment>#.+)?" 27 r")" 28 r"\s*" 29 r"$") 30 # match section name, followed by opening bracked, followed by EOL, 31 # optionally with whitespace in between each item 32 section_begin_regex = re.compile(r"\s*" 33 r"(?P<version>[a-zA-Z0-9_\.]+)" 34 r"\s*" 35 r"{" 36 r"\s*" 37 r"$") 38 # match closing bracket, optionally followed by section name (for when we 39 # inherit from another ABI version), followed by semicolon, followed by 40 # EOL, optionally with whitespace in between each item 41 section_end_regex = re.compile(r"\s*" 42 r"}" 43 r"\s*" 44 r"(?P<parent>[a-zA-Z0-9_\.]+)?" 45 r"\s*" 46 r";" 47 r"\s*" 48 r"$") 49 50 # for stable ABI, we don't care about which version introduced which 51 # function, we just flatten the list. there are dupes in certain files, so 52 # use a set instead of a list 53 stable_lines = set() 54 # copy experimental section as is 55 experimental_lines = [] 56 # copy internal section as is 57 internal_lines = [] 58 in_experimental = False 59 in_internal = False 60 has_stable = False 61 62 # gather all functions 63 for line in f_in: 64 # clean up the line 65 line = line.strip('\n').strip() 66 67 # is this an end of section? 68 match = section_end_regex.match(line) 69 if match: 70 # whatever section this was, it's not active any more 71 in_experimental = False 72 in_internal = False 73 continue 74 75 # if we're in the middle of experimental section, we need to copy 76 # the section verbatim, so just add the line 77 if in_experimental: 78 experimental_lines += [line] 79 continue 80 81 # if we're in the middle of internal section, we need to copy 82 # the section verbatim, so just add the line 83 if in_internal: 84 internal_lines += [line] 85 continue 86 87 # skip empty lines 88 if not line: 89 continue 90 91 # is this a beginning of a new section? 92 match = section_begin_regex.match(line) 93 if match: 94 cur_section = match.group("version") 95 # is it experimental? 96 in_experimental = cur_section == "EXPERIMENTAL" 97 # is it internal? 98 in_internal = cur_section == "INTERNAL" 99 if not in_experimental and not in_internal: 100 has_stable = True 101 continue 102 103 # is this a function? 104 match = func_line_regex.match(line) 105 if match: 106 stable_lines.add(match.group("line")) 107 108 return has_stable, stable_lines, experimental_lines, internal_lines 109 110 111def __generate_stable_abi(f_out, abi_major, lines): 112 # print ABI version header 113 print("DPDK_{} {{".format(abi_major), file=f_out) 114 115 # print global section if it exists 116 if lines: 117 print("\tglobal:", file=f_out) 118 # blank line 119 print(file=f_out) 120 121 # print all stable lines, alphabetically sorted 122 for line in sorted(lines): 123 print("\t{}".format(line), file=f_out) 124 125 # another blank line 126 print(file=f_out) 127 128 # print local section 129 print("\tlocal: *;", file=f_out) 130 131 # end stable version 132 print("};", file=f_out) 133 134 135def __generate_experimental_abi(f_out, lines): 136 # start experimental section 137 print("EXPERIMENTAL {", file=f_out) 138 139 # print all experimental lines as they were 140 for line in lines: 141 # don't print empty whitespace 142 if not line: 143 print("", file=f_out) 144 else: 145 print("\t{}".format(line), file=f_out) 146 147 # end section 148 print("};", file=f_out) 149 150def __generate_internal_abi(f_out, lines): 151 # start internal section 152 print("INTERNAL {", file=f_out) 153 154 # print all internal lines as they were 155 for line in lines: 156 # don't print empty whitespace 157 if not line: 158 print("", file=f_out) 159 else: 160 print("\t{}".format(line), file=f_out) 161 162 # end section 163 print("};", file=f_out) 164 165def __main(): 166 arg_parser = argparse.ArgumentParser( 167 description='Merge versions in linker version script.') 168 169 arg_parser.add_argument("map_file", type=str, 170 help='path to linker version script file ' 171 '(pattern: version.map)') 172 arg_parser.add_argument("abi_version", type=str, 173 help='target ABI version (pattern: MAJOR.MINOR)') 174 175 parsed = arg_parser.parse_args() 176 177 if not parsed.map_file.endswith('version.map'): 178 print("Invalid input file: {}".format(parsed.map_file), 179 file=sys.stderr) 180 arg_parser.print_help() 181 sys.exit(1) 182 183 if not re.match(r"\d{1,2}\.\d{1,2}", parsed.abi_version): 184 print("Invalid ABI version: {}".format(parsed.abi_version), 185 file=sys.stderr) 186 arg_parser.print_help() 187 sys.exit(1) 188 abi_major = parsed.abi_version.split('.')[0] 189 190 with open(parsed.map_file) as f_in: 191 has_stable, stable_lines, experimental_lines, internal_lines = __parse_map_file(f_in) 192 193 with open(parsed.map_file, 'w') as f_out: 194 need_newline = has_stable and experimental_lines 195 if has_stable: 196 __generate_stable_abi(f_out, abi_major, stable_lines) 197 if need_newline: 198 # separate sections with a newline 199 print(file=f_out) 200 if experimental_lines: 201 __generate_experimental_abi(f_out, experimental_lines) 202 if internal_lines: 203 if has_stable or experimental_lines: 204 # separate sections with a newline 205 print(file=f_out) 206 __generate_internal_abi(f_out, internal_lines) 207 208 209if __name__ == "__main__": 210 __main() 211