xref: /dpdk/buildtools/dpdk-cmdline-gen.py (revision da7e701151ea8b742d4c38ace3e4fefd1b4507fc)
1#!/usr/bin/env python3
2# SPDX-License-Identifier: BSD-3-Clause
3# Copyright(c) 2023 Intel Corporation
4#
5"""
6Script to automatically generate boilerplate for using DPDK cmdline library.
7"""
8
9import argparse
10import sys
11
12PARSE_FN_PARAMS = "void *parsed_result, struct cmdline *cl, void *data"
13PARSE_FN_BODY = """
14    /* TODO: command action */
15    RTE_SET_USED(parsed_result);
16    RTE_SET_USED(cl);
17    RTE_SET_USED(data);
18"""
19NUMERIC_TYPES = [
20    "UINT8",
21    "UINT16",
22    "UINT32",
23    "UINT64",
24    "INT8",
25    "INT16",
26    "INT32",
27    "INT64",
28]
29
30
31def process_command(lineno, tokens, comment):
32    """Generate the structures and definitions for a single command."""
33    out = []
34    cfile_out = []
35
36    if tokens[0].startswith("<"):
37        raise ValueError(f"Error line {lineno + 1}: command must start with a literal string")
38
39    name_tokens = []
40    for t in tokens:
41        if t.startswith("<"):
42            break
43        name_tokens.append(t)
44    name = "_".join(name_tokens)
45
46    result_struct = []
47    initializers = []
48    token_list = []
49    for t in tokens:
50        if t.startswith("<"):
51            t_type, t_name = t[1:].split(">")
52            t_val = "NULL"
53        else:
54            t_type = "STRING"
55            t_name = t
56            t_val = f'"{t}"'
57
58        if t_type == "STRING":
59            result_struct.append(f"\tcmdline_fixed_string_t {t_name};")
60            initializers.append(
61                f"static cmdline_parse_token_string_t cmd_{name}_{t_name}_tok =\n"
62                f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});"
63            )
64        elif t_type in NUMERIC_TYPES:
65            result_struct.append(f"\t{t_type.lower()}_t {t_name};")
66            initializers.append(
67                f"static cmdline_parse_token_num_t cmd_{name}_{t_name}_tok =\n"
68                f"\tTOKEN_NUM_INITIALIZER(struct cmd_{name}_result, {t_name}, RTE_{t_type});"
69            )
70        elif t_type in ["IP", "IP_ADDR", "IPADDR"]:
71            result_struct.append(f"\tcmdline_ipaddr_t {t_name};")
72            initializers.append(
73                f"cmdline_parse_token_ipaddr_t cmd_{name}_{t_name}_tok =\n"
74                f"\tTOKEN_IPV4_INITIALIZER(struct cmd_{name}_result, {t_name});"
75            )
76        elif t_type.startswith("(") and t_type.endswith(")"):
77            result_struct.append(f"\tcmdline_fixed_string_t {t_name};")
78            t_val = f'"{t_type[1:-1].replace(",","#")}"'
79            initializers.append(
80                f"static cmdline_parse_token_string_t cmd_{name}_{t_name}_tok =\n"
81                f"\tTOKEN_STRING_INITIALIZER(struct cmd_{name}_result, {t_name}, {t_val});"
82            )
83        else:
84            raise TypeError(f"Error line {lineno + 1}: unknown token type '{t_type}'")
85        token_list.append(f"cmd_{name}_{t_name}_tok")
86
87    out.append(f'/* Auto-generated handling for command "{" ".join(tokens)}" */')
88    # output function prototype
89    func_sig = f"void\ncmd_{name}_parsed({PARSE_FN_PARAMS})"
90    out.append(f"extern {func_sig};\n")
91    # output result data structure
92    out.append(f"struct cmd_{name}_result {{\n" + "\n".join(result_struct) + "\n};\n")
93    # output the initializer tokens
94    out.append("\n".join(initializers) + "\n")
95    # output the instance structure
96    inst_elems = "\n".join([f"\t\t(void *)&{t}," for t in token_list])
97    out.append(
98        f"""\
99static cmdline_parse_inst_t cmd_{name} = {{
100\t.f = cmd_{name}_parsed,
101\t.data = NULL,
102\t.help_str = "{comment}",
103\t.tokens = {{
104{inst_elems}
105\t\tNULL,
106\t}}
107}};
108"""
109    )
110    # output function template if C file being written
111    cfile_out.append(f"{func_sig}\n{{{PARSE_FN_BODY}}}\n")
112
113    # return the instance structure name
114    return (f"cmd_{name}", out, cfile_out)
115
116
117def process_commands(infile, hfile, cfile, ctxname):
118    """Generate boilerplate output for a list of commands from infile."""
119    instances = []
120
121    hfile.write(
122        f"""\
123/* File autogenerated by {sys.argv[0]} */
124#ifndef GENERATED_COMMANDS_H
125#define GENERATED_COMMANDS_H
126#include <rte_common.h>
127#include <cmdline.h>
128#include <cmdline_parse_string.h>
129#include <cmdline_parse_num.h>
130#include <cmdline_parse_ipaddr.h>
131
132"""
133    )
134
135    for lineno, line in enumerate(infile.readlines()):
136        if line.lstrip().startswith("#"):
137            continue
138        if "#" not in line:
139            line = line + "#"  # ensure split always works, even if no help text
140        tokens, comment = line.split("#", 1)
141        cmd_inst, h_out, c_out = process_command(lineno, tokens.strip().split(), comment.strip())
142        hfile.write("\n".join(h_out))
143        if cfile:
144            cfile.write("\n".join(c_out))
145        instances.append(cmd_inst)
146
147    inst_join_str = ",\n\t&"
148    hfile.write(
149        f"""
150static __rte_used cmdline_parse_ctx_t {ctxname}[] = {{
151\t&{inst_join_str.join(instances)},
152\tNULL
153}};
154
155#endif /* GENERATED_COMMANDS_H */
156"""
157    )
158
159
160def main():
161    """Application main entry point."""
162    ap = argparse.ArgumentParser(description=__doc__)
163    ap.add_argument(
164        "--stubs",
165        action="store_true",
166        help="Produce C file with empty function stubs for each command",
167    )
168    ap.add_argument(
169        "--output-file",
170        "-o",
171        default="-",
172        help="Output header filename [default to stdout]",
173    )
174    ap.add_argument(
175        "--context-name",
176        default="ctx",
177        help="Name given to the cmdline context variable in the output header [default=ctx]",
178    )
179    ap.add_argument("infile", type=argparse.FileType("r"), help="File with list of commands")
180    args = ap.parse_args()
181
182    if not args.stubs:
183        if args.output_file == "-":
184            process_commands(args.infile, sys.stdout, None, args.context_name)
185        else:
186            with open(args.output_file, "w") as hfile:
187                process_commands(args.infile, hfile, None, args.context_name)
188    else:
189        if not args.output_file.endswith(".h"):
190            ap.error(
191                "-o/--output-file: specify an output filename ending with .h when creating stubs"
192            )
193
194        cfilename = args.output_file[:-2] + ".c"
195        with open(args.output_file, "w") as hfile:
196            with open(cfilename, "w") as cfile:
197                cfile.write(f'#include "{args.output_file}"\n\n')
198                process_commands(args.infile, hfile, cfile, args.context_name)
199
200
201if __name__ == "__main__":
202    main()
203