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