xref: /dpdk/devtools/update_version_map_abi.py (revision 10b71caecbe1cddcbb65c050ca775fba575e88db)
1#!/usr/bin/env python
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
12from __future__ import print_function
13import argparse
14import sys
15import re
16
17
18def __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
108def __generate_stable_abi(f_out, abi_major, lines):
109    # print ABI version header
110    print("DPDK_{} {{".format(abi_major), 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
132def __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
147def __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
162def __main():
163    if sys.version_info.major < 3:
164        print("WARNING: Python 2 is deprecated for use in DPDK, and will not work in future releases.", file=sys.stderr)
165        print("Please use Python 3 instead", file=sys.stderr)
166
167    arg_parser = argparse.ArgumentParser(
168        description='Merge versions in linker version script.')
169
170    arg_parser.add_argument("map_file", type=str,
171                            help='path to linker version script file '
172                                 '(pattern: *version.map)')
173    arg_parser.add_argument("abi_version", type=str,
174                            help='target ABI version (pattern: MAJOR.MINOR)')
175
176    parsed = arg_parser.parse_args()
177
178    if not parsed.map_file.endswith('version.map'):
179        print("Invalid input file: {}".format(parsed.map_file),
180              file=sys.stderr)
181        arg_parser.print_help()
182        sys.exit(1)
183
184    if not re.match(r"\d{1,2}\.\d{1,2}", parsed.abi_version):
185        print("Invalid ABI version: {}".format(parsed.abi_version),
186              file=sys.stderr)
187        arg_parser.print_help()
188        sys.exit(1)
189    abi_major = parsed.abi_version.split('.')[0]
190
191    with open(parsed.map_file) as f_in:
192        has_stable, stable_lines, experimental_lines, internal_lines = __parse_map_file(f_in)
193
194    with open(parsed.map_file, 'w') as f_out:
195        need_newline = has_stable and experimental_lines
196        if has_stable:
197            __generate_stable_abi(f_out, abi_major, stable_lines)
198        if need_newline:
199            # separate sections with a newline
200            print(file=f_out)
201        if experimental_lines:
202            __generate_experimental_abi(f_out, experimental_lines)
203        if internal_lines:
204            if has_stable or experimental_lines:
205              # separate sections with a newline
206              print(file=f_out)
207            __generate_internal_abi(f_out, internal_lines)
208
209
210if __name__ == "__main__":
211    __main()
212