1*2d9fd380Sjfb8856606#!/usr/bin/env python3
24418919fSjohnjiang# SPDX-License-Identifier: BSD-3-Clause
34418919fSjohnjiang# Copyright(c) 2019 Intel Corporation
44418919fSjohnjiang
54418919fSjohnjiang"""
64418919fSjohnjiangA Python program that updates and merges all available stable ABI versions into
74418919fSjohnjiangone ABI version, while leaving experimental ABI exactly as it is. The intended
84418919fSjohnjiangABI version is supplied via command-line parameter. This script is to be called
94418919fSjohnjiangfrom the devtools/update-abi.sh utility.
104418919fSjohnjiang"""
114418919fSjohnjiang
124418919fSjohnjiangimport argparse
134418919fSjohnjiangimport sys
144418919fSjohnjiangimport re
154418919fSjohnjiang
164418919fSjohnjiang
174418919fSjohnjiangdef __parse_map_file(f_in):
184418919fSjohnjiang    # match function name, followed by semicolon, followed by EOL, optionally
194418919fSjohnjiang    # with whitespace in between each item
204418919fSjohnjiang    func_line_regex = re.compile(r"\s*"
214418919fSjohnjiang                                 r"(?P<func>[a-zA-Z_0-9]+)"
224418919fSjohnjiang                                 r"\s*"
234418919fSjohnjiang                                 r";"
244418919fSjohnjiang                                 r"\s*"
254418919fSjohnjiang                                 r"$")
264418919fSjohnjiang    # match section name, followed by opening bracked, followed by EOL,
274418919fSjohnjiang    # optionally with whitespace in between each item
284418919fSjohnjiang    section_begin_regex = re.compile(r"\s*"
294418919fSjohnjiang                                     r"(?P<version>[a-zA-Z0-9_\.]+)"
304418919fSjohnjiang                                     r"\s*"
314418919fSjohnjiang                                     r"{"
324418919fSjohnjiang                                     r"\s*"
334418919fSjohnjiang                                     r"$")
344418919fSjohnjiang    # match closing bracket, optionally followed by section name (for when we
354418919fSjohnjiang    # inherit from another ABI version), followed by semicolon, followed by
364418919fSjohnjiang    # EOL, optionally with whitespace in between each item
374418919fSjohnjiang    section_end_regex = re.compile(r"\s*"
384418919fSjohnjiang                                   r"}"
394418919fSjohnjiang                                   r"\s*"
404418919fSjohnjiang                                   r"(?P<parent>[a-zA-Z0-9_\.]+)?"
414418919fSjohnjiang                                   r"\s*"
424418919fSjohnjiang                                   r";"
434418919fSjohnjiang                                   r"\s*"
444418919fSjohnjiang                                   r"$")
454418919fSjohnjiang
464418919fSjohnjiang    # for stable ABI, we don't care about which version introduced which
474418919fSjohnjiang    # function, we just flatten the list. there are dupes in certain files, so
484418919fSjohnjiang    # use a set instead of a list
494418919fSjohnjiang    stable_lines = set()
504418919fSjohnjiang    # copy experimental section as is
514418919fSjohnjiang    experimental_lines = []
52*2d9fd380Sjfb8856606    # copy internal section as is
53*2d9fd380Sjfb8856606    internal_lines = []
544418919fSjohnjiang    in_experimental = False
55*2d9fd380Sjfb8856606    in_internal = False
564418919fSjohnjiang    has_stable = False
574418919fSjohnjiang
584418919fSjohnjiang    # gather all functions
594418919fSjohnjiang    for line in f_in:
604418919fSjohnjiang        # clean up the line
614418919fSjohnjiang        line = line.strip('\n').strip()
624418919fSjohnjiang
634418919fSjohnjiang        # is this an end of section?
644418919fSjohnjiang        match = section_end_regex.match(line)
654418919fSjohnjiang        if match:
664418919fSjohnjiang            # whatever section this was, it's not active any more
674418919fSjohnjiang            in_experimental = False
68*2d9fd380Sjfb8856606            in_internal = False
694418919fSjohnjiang            continue
704418919fSjohnjiang
714418919fSjohnjiang        # if we're in the middle of experimental section, we need to copy
724418919fSjohnjiang        # the section verbatim, so just add the line
734418919fSjohnjiang        if in_experimental:
744418919fSjohnjiang            experimental_lines += [line]
754418919fSjohnjiang            continue
764418919fSjohnjiang
77*2d9fd380Sjfb8856606        # if we're in the middle of internal section, we need to copy
78*2d9fd380Sjfb8856606        # the section verbatim, so just add the line
79*2d9fd380Sjfb8856606        if in_internal:
80*2d9fd380Sjfb8856606            internal_lines += [line]
81*2d9fd380Sjfb8856606            continue
82*2d9fd380Sjfb8856606
834418919fSjohnjiang        # skip empty lines
844418919fSjohnjiang        if not line:
854418919fSjohnjiang            continue
864418919fSjohnjiang
874418919fSjohnjiang        # is this a beginning of a new section?
884418919fSjohnjiang        match = section_begin_regex.match(line)
894418919fSjohnjiang        if match:
904418919fSjohnjiang            cur_section = match.group("version")
914418919fSjohnjiang            # is it experimental?
924418919fSjohnjiang            in_experimental = cur_section == "EXPERIMENTAL"
93*2d9fd380Sjfb8856606            # is it internal?
94*2d9fd380Sjfb8856606            in_internal = cur_section == "INTERNAL"
95*2d9fd380Sjfb8856606            if not in_experimental and not in_internal:
964418919fSjohnjiang                has_stable = True
974418919fSjohnjiang            continue
984418919fSjohnjiang
994418919fSjohnjiang        # is this a function?
1004418919fSjohnjiang        match = func_line_regex.match(line)
1014418919fSjohnjiang        if match:
1024418919fSjohnjiang            stable_lines.add(match.group("func"))
1034418919fSjohnjiang
104*2d9fd380Sjfb8856606    return has_stable, stable_lines, experimental_lines, internal_lines
1054418919fSjohnjiang
1064418919fSjohnjiang
107*2d9fd380Sjfb8856606def __generate_stable_abi(f_out, abi_major, lines):
1084418919fSjohnjiang    # print ABI version header
109*2d9fd380Sjfb8856606    print("DPDK_{} {{".format(abi_major), file=f_out)
1104418919fSjohnjiang
1114418919fSjohnjiang    # print global section if it exists
1124418919fSjohnjiang    if lines:
1134418919fSjohnjiang        print("\tglobal:", file=f_out)
1144418919fSjohnjiang        # blank line
1154418919fSjohnjiang        print(file=f_out)
1164418919fSjohnjiang
1174418919fSjohnjiang        # print all stable lines, alphabetically sorted
1184418919fSjohnjiang        for line in sorted(lines):
1194418919fSjohnjiang            print("\t{};".format(line), file=f_out)
1204418919fSjohnjiang
1214418919fSjohnjiang        # another blank line
1224418919fSjohnjiang        print(file=f_out)
1234418919fSjohnjiang
1244418919fSjohnjiang    # print local section
1254418919fSjohnjiang    print("\tlocal: *;", file=f_out)
1264418919fSjohnjiang
1274418919fSjohnjiang    # end stable version
1284418919fSjohnjiang    print("};", file=f_out)
1294418919fSjohnjiang
1304418919fSjohnjiang
1314418919fSjohnjiangdef __generate_experimental_abi(f_out, lines):
1324418919fSjohnjiang    # start experimental section
1334418919fSjohnjiang    print("EXPERIMENTAL {", file=f_out)
1344418919fSjohnjiang
1354418919fSjohnjiang    # print all experimental lines as they were
1364418919fSjohnjiang    for line in lines:
1374418919fSjohnjiang        # don't print empty whitespace
1384418919fSjohnjiang        if not line:
1394418919fSjohnjiang            print("", file=f_out)
1404418919fSjohnjiang        else:
1414418919fSjohnjiang            print("\t{}".format(line), file=f_out)
1424418919fSjohnjiang
1434418919fSjohnjiang    # end section
1444418919fSjohnjiang    print("};", file=f_out)
1454418919fSjohnjiang
146*2d9fd380Sjfb8856606def __generate_internal_abi(f_out, lines):
147*2d9fd380Sjfb8856606    # start internal section
148*2d9fd380Sjfb8856606    print("INTERNAL {", file=f_out)
149*2d9fd380Sjfb8856606
150*2d9fd380Sjfb8856606    # print all internal lines as they were
151*2d9fd380Sjfb8856606    for line in lines:
152*2d9fd380Sjfb8856606        # don't print empty whitespace
153*2d9fd380Sjfb8856606        if not line:
154*2d9fd380Sjfb8856606            print("", file=f_out)
155*2d9fd380Sjfb8856606        else:
156*2d9fd380Sjfb8856606            print("\t{}".format(line), file=f_out)
157*2d9fd380Sjfb8856606
158*2d9fd380Sjfb8856606    # end section
159*2d9fd380Sjfb8856606    print("};", file=f_out)
1604418919fSjohnjiang
1614418919fSjohnjiangdef __main():
1624418919fSjohnjiang    arg_parser = argparse.ArgumentParser(
1634418919fSjohnjiang        description='Merge versions in linker version script.')
1644418919fSjohnjiang
1654418919fSjohnjiang    arg_parser.add_argument("map_file", type=str,
1664418919fSjohnjiang                            help='path to linker version script file '
167*2d9fd380Sjfb8856606                                 '(pattern: version.map)')
1684418919fSjohnjiang    arg_parser.add_argument("abi_version", type=str,
1694418919fSjohnjiang                            help='target ABI version (pattern: MAJOR.MINOR)')
1704418919fSjohnjiang
1714418919fSjohnjiang    parsed = arg_parser.parse_args()
1724418919fSjohnjiang
1734418919fSjohnjiang    if not parsed.map_file.endswith('version.map'):
1744418919fSjohnjiang        print("Invalid input file: {}".format(parsed.map_file),
1754418919fSjohnjiang              file=sys.stderr)
1764418919fSjohnjiang        arg_parser.print_help()
1774418919fSjohnjiang        sys.exit(1)
1784418919fSjohnjiang
1794418919fSjohnjiang    if not re.match(r"\d{1,2}\.\d{1,2}", parsed.abi_version):
1804418919fSjohnjiang        print("Invalid ABI version: {}".format(parsed.abi_version),
1814418919fSjohnjiang              file=sys.stderr)
1824418919fSjohnjiang        arg_parser.print_help()
1834418919fSjohnjiang        sys.exit(1)
184*2d9fd380Sjfb8856606    abi_major = parsed.abi_version.split('.')[0]
1854418919fSjohnjiang
1864418919fSjohnjiang    with open(parsed.map_file) as f_in:
187*2d9fd380Sjfb8856606        has_stable, stable_lines, experimental_lines, internal_lines = __parse_map_file(f_in)
1884418919fSjohnjiang
1894418919fSjohnjiang    with open(parsed.map_file, 'w') as f_out:
1904418919fSjohnjiang        need_newline = has_stable and experimental_lines
1914418919fSjohnjiang        if has_stable:
192*2d9fd380Sjfb8856606            __generate_stable_abi(f_out, abi_major, stable_lines)
1934418919fSjohnjiang        if need_newline:
1944418919fSjohnjiang            # separate sections with a newline
1954418919fSjohnjiang            print(file=f_out)
1964418919fSjohnjiang        if experimental_lines:
1974418919fSjohnjiang            __generate_experimental_abi(f_out, experimental_lines)
198*2d9fd380Sjfb8856606        if internal_lines:
199*2d9fd380Sjfb8856606            if has_stable or experimental_lines:
200*2d9fd380Sjfb8856606              # separate sections with a newline
201*2d9fd380Sjfb8856606              print(file=f_out)
202*2d9fd380Sjfb8856606            __generate_internal_abi(f_out, internal_lines)
2034418919fSjohnjiang
2044418919fSjohnjiang
2054418919fSjohnjiangif __name__ == "__main__":
2064418919fSjohnjiang    __main()
207