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