xref: /dpdk/devtools/update_version_map_abi.py (revision f5057be340e44f3edc0fe90fa875eb89a4c49b4f)
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, optionally
19    # with whitespace in between each item
20    func_line_regex = re.compile(r"\s*"
21                                 r"(?P<func>[a-zA-Z_0-9]+)"
22                                 r"\s*"
23                                 r";"
24                                 r"\s*"
25                                 r"$")
26    # match section name, followed by opening bracked, followed by EOL,
27    # optionally with whitespace in between each item
28    section_begin_regex = re.compile(r"\s*"
29                                     r"(?P<version>[a-zA-Z0-9_\.]+)"
30                                     r"\s*"
31                                     r"{"
32                                     r"\s*"
33                                     r"$")
34    # match closing bracket, optionally followed by section name (for when we
35    # inherit from another ABI version), followed by semicolon, followed by
36    # EOL, optionally with whitespace in between each item
37    section_end_regex = re.compile(r"\s*"
38                                   r"}"
39                                   r"\s*"
40                                   r"(?P<parent>[a-zA-Z0-9_\.]+)?"
41                                   r"\s*"
42                                   r";"
43                                   r"\s*"
44                                   r"$")
45
46    # for stable ABI, we don't care about which version introduced which
47    # function, we just flatten the list. there are dupes in certain files, so
48    # use a set instead of a list
49    stable_lines = set()
50    # copy experimental section as is
51    experimental_lines = []
52    # copy internal section as is
53    internal_lines = []
54    in_experimental = False
55    in_internal = False
56    has_stable = False
57
58    # gather all functions
59    for line in f_in:
60        # clean up the line
61        line = line.strip('\n').strip()
62
63        # is this an end of section?
64        match = section_end_regex.match(line)
65        if match:
66            # whatever section this was, it's not active any more
67            in_experimental = False
68            in_internal = False
69            continue
70
71        # if we're in the middle of experimental section, we need to copy
72        # the section verbatim, so just add the line
73        if in_experimental:
74            experimental_lines += [line]
75            continue
76
77        # if we're in the middle of internal section, we need to copy
78        # the section verbatim, so just add the line
79        if in_internal:
80            internal_lines += [line]
81            continue
82
83        # skip empty lines
84        if not line:
85            continue
86
87        # is this a beginning of a new section?
88        match = section_begin_regex.match(line)
89        if match:
90            cur_section = match.group("version")
91            # is it experimental?
92            in_experimental = cur_section == "EXPERIMENTAL"
93            # is it internal?
94            in_internal = cur_section == "INTERNAL"
95            if not in_experimental and not in_internal:
96                has_stable = True
97            continue
98
99        # is this a function?
100        match = func_line_regex.match(line)
101        if match:
102            stable_lines.add(match.group("func"))
103
104    return has_stable, stable_lines, experimental_lines, internal_lines
105
106
107def __generate_stable_abi(f_out, abi_major, lines):
108    # print ABI version header
109    print("DPDK_{} {{".format(abi_major), file=f_out)
110
111    # print global section if it exists
112    if lines:
113        print("\tglobal:", file=f_out)
114        # blank line
115        print(file=f_out)
116
117        # print all stable lines, alphabetically sorted
118        for line in sorted(lines):
119            print("\t{};".format(line), file=f_out)
120
121        # another blank line
122        print(file=f_out)
123
124    # print local section
125    print("\tlocal: *;", file=f_out)
126
127    # end stable version
128    print("};", file=f_out)
129
130
131def __generate_experimental_abi(f_out, lines):
132    # start experimental section
133    print("EXPERIMENTAL {", file=f_out)
134
135    # print all experimental lines as they were
136    for line in lines:
137        # don't print empty whitespace
138        if not line:
139            print("", file=f_out)
140        else:
141            print("\t{}".format(line), file=f_out)
142
143    # end section
144    print("};", file=f_out)
145
146def __generate_internal_abi(f_out, lines):
147    # start internal section
148    print("INTERNAL {", file=f_out)
149
150    # print all internal lines as they were
151    for line in lines:
152        # don't print empty whitespace
153        if not line:
154            print("", file=f_out)
155        else:
156            print("\t{}".format(line), file=f_out)
157
158    # end section
159    print("};", file=f_out)
160
161def __main():
162    arg_parser = argparse.ArgumentParser(
163        description='Merge versions in linker version script.')
164
165    arg_parser.add_argument("map_file", type=str,
166                            help='path to linker version script file '
167                                 '(pattern: *version.map)')
168    arg_parser.add_argument("abi_version", type=str,
169                            help='target ABI version (pattern: MAJOR.MINOR)')
170
171    parsed = arg_parser.parse_args()
172
173    if not parsed.map_file.endswith('version.map'):
174        print("Invalid input file: {}".format(parsed.map_file),
175              file=sys.stderr)
176        arg_parser.print_help()
177        sys.exit(1)
178
179    if not re.match(r"\d{1,2}\.\d{1,2}", parsed.abi_version):
180        print("Invalid ABI version: {}".format(parsed.abi_version),
181              file=sys.stderr)
182        arg_parser.print_help()
183        sys.exit(1)
184    abi_major = parsed.abi_version.split('.')[0]
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, abi_major, 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
205if __name__ == "__main__":
206    __main()
207