xref: /dpdk/devtools/update_version_map_abi.py (revision cd346367f898d619edf53f13628d6e539dbcab40)
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_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
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    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
205if __name__ == "__main__":
206    __main()
207