xref: /dpdk/devtools/update_version_map_abi.py (revision 56ea803e878e06ac1c7056165b960416666086f4)
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