xref: /openbmc/sdbusplus/tools/sdbus++-gen-meson (revision 0336a2fc)
1#!/usr/bin/env bash
2
3set -e
4
5# Locale can change behavior of utilities like 'sort' but we want the output
6# to be stable on all machines.  Force the locale to 'C' for consistency.
7export LC_ALL=C
8
9function show_usage() {
10    cat \
11        << EOF
12Usage: $(basename "$0") [options] <command-args>*
13
14Generate meson.build files from a directory tree containing YAML files and
15facilitate building the sdbus++ sources.
16
17Options:
18    --help              - Display this message
19    --command <cmd>     - Command mode to execute (default 'meson').
20    --directory <path>  - Root directory of the YAML source (default '.').
21    --output <path>     - Root directory of the output (default '.').
22    --tool <path>       - Path to the processing tool (default 'sdbus++').
23    --version           - Display this tool's version string.
24
25Commands:
26    meson               - Generate a tree of meson.build files corresponding
27                          to the source YAML files.
28    cpp <intf>          - Generate the source files from a YAML interface.
29    markdown <intf>     - Generate the markdown files from a YAML interface.
30    version             - Display this tool's version string.
31
32EOF
33}
34
35## The version is somewhat arbitrary but is used to create a warning message
36## if a repository contains old copies of the generated meson.build files and
37## needs an update.  We should increment the version number whenever the
38## resulting meson.build would change.
39tool_version="sdbus++-gen-meson version 8"
40function show_version() {
41    echo "${tool_version}"
42}
43
44# Set up defaults.
45sdbuspp="sdbus++"
46outputdir="."
47cmd="meson"
48rootdir="."
49
50# Parse options.
51options="$(getopt -o hc:d:o:t:v --long help,command:,directory:,output:,tool:,version -- "$@")"
52eval set -- "${options}"
53
54while true; do
55    case "$1" in
56        -h | --help)
57            show_usage
58            exit
59            ;;
60
61        -c | --command)
62            shift
63            cmd="$1"
64            shift
65            ;;
66
67        -d | --directory)
68            shift
69            rootdir="$1"
70            shift
71            ;;
72
73        -o | --output)
74            shift
75            outputdir="$1"
76            shift
77            ;;
78
79        -t | --tool)
80            shift
81            sdbuspp="$1"
82            shift
83            ;;
84
85        -v | --version)
86            show_version
87            exit
88            ;;
89
90        --)
91            shift
92            break
93            ;;
94
95        *)
96            echo "Invalid argument $1"
97            exit 1
98            ;;
99    esac
100done
101
102## Create an initially empty meson.build file.
103## $1 - path to create meson.build at.
104function meson_empty_file() {
105    mkdir -p "$1"
106    echo "# Generated file; do not modify." > "$1/meson.build"
107}
108
109## Create the root-level meson.build
110##
111## Inserts rules to run the available version of this tool to ensure the
112## version has not changed.
113function meson_create_root() {
114    meson_empty_file "${outputdir}"
115
116    cat >> "${outputdir}/meson.build" \
117        << EOF
118sdbuspp_gen_meson_ver = run_command(
119    sdbuspp_gen_meson_prog,
120    '--version',
121    check: true,
122).stdout().strip().split('\n')[0]
123
124if sdbuspp_gen_meson_ver != '${tool_version}'
125    warning('Generated meson files from wrong version of sdbus++-gen-meson.')
126    warning(
127        'Expected "${tool_version}", got:',
128        sdbuspp_gen_meson_ver
129    )
130endif
131
132EOF
133}
134
135## hash-tables to store:
136##      meson_paths - list of subdirectory paths for which an empty meson.build
137##                    has already been created.
138##      interfaces - list of interface paths which a YAML has been found and
139##                   which YAML types (interface, errors, etc.).
140declare -A meson_paths
141declare -A interfaces
142
143## Ensure the meson.build files to a path have been created.
144## $1 - The path requiring to be created.
145function meson_create_path() {
146
147    meson_path="${outputdir}"
148    prev_meson_path=""
149
150    # Split the path into segments.
151    for part in $(echo "$1" | tr '/' '\n'); do
152        prev_meson_path="${meson_path}"
153        meson_path="${meson_path}/${part}"
154
155        # Create the meson.build for this segment if it doesn't already exist.
156        if [[ "" == "${meson_paths[${meson_path}]}" ]]; then
157            meson_paths["${meson_path}"]="1"
158            meson_empty_file "${meson_path}"
159
160            # Add the 'subdir' link into the parent's meson.build.
161            # We need to skip adding the links into the 'root' meson.build
162            # because most repositories want to selectively add TLDs based
163            # on config flags.  Let them figure out their own logic for that.
164            if [[ ${outputdir} != "${prev_meson_path}" ]]; then
165                echo "subdir('${part}')" >> "${prev_meson_path}/meson.build"
166            fi
167        fi
168    done
169}
170
171## Generate the meson target for the source files (.cpp/.hpp) from a YAML
172## interface.
173##
174## $1 - The interface to generate a target for.
175function meson_cpp_target() {
176    mesondir="${outputdir}/$1"
177    yamldir="$(realpath --relative-to="${mesondir}" "${rootdir}")"
178
179    # Determine the source and output files based on the YAMLs present.
180    sources=""
181    outputs=""
182    for s in ${interfaces[$1]}; do
183        sources="'${yamldir}/$1.${s}', "
184
185        case "${s}" in
186            errors.yaml)
187                outputs="${outputs}'error.cpp', 'error.hpp', "
188                ;;
189
190            events.yaml)
191                outputs="${outputs}'event.cpp', 'event.hpp', "
192                ;;
193
194            interface.yaml)
195                outputs="${outputs}'common.hpp', "
196                outputs="${outputs}'server.cpp', 'server.hpp', "
197                outputs="${outputs}'aserver.hpp', "
198                outputs="${outputs}'client.hpp', "
199                ;;
200
201            *)
202                echo "Unknown interface type: ${s}"
203                exit 1
204                ;;
205        esac
206    done
207
208    # Create the target to generate the 'outputs'.
209    cat >> "${mesondir}/meson.build" \
210        << EOF
211generated_sources += custom_target(
212    '$1__cpp'.underscorify(),
213    input: [ ${sources} ],
214    output: [ ${outputs} ],
215    depend_files: sdbusplusplus_depfiles,
216    command: [
217        sdbuspp_gen_meson_prog, '--command', 'cpp',
218        '--output', meson.current_build_dir(),
219        '--tool', sdbusplusplus_prog,
220        '--directory', meson.current_source_dir() / '${yamldir}',
221        '$1',
222    ],
223)
224
225EOF
226}
227
228## Generate the meson target for the markdown files from a YAML interface.
229## $1 - The interface to generate a target for.
230function meson_md_target() {
231    mesondir="${outputdir}/$(dirname "$1")"
232    yamldir="$(realpath --relative-to="${mesondir}" "${rootdir}")"
233
234    # Determine the source files based on the YAMLs present.
235    sources=""
236    for s in ${interfaces[$1]}; do
237        sources="${sources}'${yamldir}/$1.${s}', "
238    done
239
240    # Create the target to generate the interface.md file.
241    cat >> "${mesondir}/meson.build" \
242        << EOF
243generated_others += custom_target(
244    '$1__markdown'.underscorify(),
245    input: [ ${sources} ],
246    output: [ '$(basename "$1").md' ],
247    depend_files: sdbusplusplus_depfiles,
248    command: [
249        sdbuspp_gen_meson_prog, '--command', 'markdown',
250        '--output', meson.current_build_dir(),
251        '--tool', sdbusplusplus_prog,
252        '--directory', meson.current_source_dir() / '${yamldir}',
253        '$1',
254    ],
255)
256
257EOF
258}
259
260## Handle command=meson by generating the tree of meson.build files.
261function cmd_meson() {
262    # Find and sort all the YAML files
263    yamls="$(find "${rootdir}" -name '*.interface.yaml' -o -name '*.errors.yaml' -o -name '*.events.yaml')"
264    yamls="$(echo "${yamls}" | sort)"
265
266    # Assign the YAML files into the hash-table by interface name.
267    for y in ${yamls}; do
268        rel="$(realpath "--relative-to=${rootdir}" "${y}")"
269        dir="$(dirname "${rel}")"
270        ext="${rel#*.}"
271        base="$(basename "${rel}" ".${ext}")"
272        key="${dir}/${base}"
273
274        interfaces["${key}"]="${interfaces[${key}]} ${ext}"
275    done
276
277    # Create the meson.build files.
278    meson_create_root
279    # shellcheck disable=SC2312
280    sorted_ifaces="$(echo "${!interfaces[@]}" | tr " " "\n" | sort)"
281    for i in ${sorted_ifaces}; do
282        meson_create_path "${i}"
283        meson_cpp_target "${i}"
284        meson_md_target "${i}"
285    done
286}
287
288## Handle command=cpp by calling sdbus++ as appropriate.
289## $1 - interface to generate.
290##
291## For an interface foo/bar, the outputdir is expected to be foo/bar.
292function cmd_cpp() {
293
294    if [[ "" == "$1" ]]; then
295        show_usage
296        exit 1
297    fi
298
299    if [[ ! -e "${rootdir}/$1.interface.yaml" ]] &&
300    [[ ! -e "${rootdir}/$1.errors.yaml" ]] &&
301    [[ ! -e "${rootdir}/$1.events.yaml" ]]; then
302        echo "Missing YAML for $1."
303        exit 1
304    fi
305
306    mkdir -p "${outputdir}"
307
308    sdbusppcmd="${sdbuspp} -r ${rootdir}"
309    intf="${1//\//.}"
310
311    if [[ -e "${rootdir}/$1.interface.yaml" ]]; then
312        ${sdbusppcmd} interface common-header "${intf}" > "${outputdir}/common.hpp"
313        ${sdbusppcmd} interface server-header "${intf}" > "${outputdir}/server.hpp"
314        ${sdbusppcmd} interface server-cpp "${intf}" > "${outputdir}/server.cpp"
315        ${sdbusppcmd} interface client-header "${intf}" > "${outputdir}/client.hpp"
316        ${sdbusppcmd} interface aserver-header "${intf}" > "${outputdir}/aserver.hpp"
317    fi
318
319    if [[ -e "${rootdir}/$1.errors.yaml" ]]; then
320        ${sdbusppcmd} error exception-header "${intf}" > "${outputdir}/error.hpp"
321        ${sdbusppcmd} error exception-cpp "${intf}" > "${outputdir}/error.cpp"
322    fi
323
324    if [[ -e "${rootdir}/$1.events.yaml" ]]; then
325        ${sdbusppcmd} event exception-header "${intf}" > "${outputdir}/event.hpp"
326        ${sdbusppcmd} event exception-cpp "${intf}" > "${outputdir}/event.cpp"
327    fi
328}
329
330## Handle command=markdown by calling sdbus++ as appropriate.
331## $1 - interface to generate.
332##
333## For an interface foo/bar, the outputdir is expected to be foo.
334function cmd_markdown() {
335
336    if [[ "" == "$1" ]]; then
337        show_usage
338        exit 1
339    fi
340
341    if [[ ! -e "${rootdir}/$1.interface.yaml" ]] &&
342    [[ ! -e "${rootdir}/$1.errors.yaml" ]] &&
343    [[ ! -e "${rootdir}/$1.events.yaml" ]]; then
344        echo "Missing YAML for $1."
345        exit 1
346    fi
347
348    mkdir -p "${outputdir}"
349
350    sdbusppcmd="${sdbuspp} -r ${rootdir}"
351    intf="${1//\//.}"
352    base="$(basename "$1")"
353
354    echo -n > "${outputdir}/${base}.md"
355    if [[ -e "${rootdir}/$1.interface.yaml" ]]; then
356        ${sdbusppcmd} interface markdown "${intf}" >> "${outputdir}/${base}.md"
357    fi
358
359    if [[ -e "${rootdir}/$1.errors.yaml" ]]; then
360        ${sdbusppcmd} error markdown "${intf}" >> "${outputdir}/${base}.md"
361    fi
362
363    if [[ -e "${rootdir}/$1.events.yaml" ]]; then
364        ${sdbusppcmd} event markdown "${intf}" >> "${outputdir}/${base}.md"
365    fi
366}
367
368## Handle command=version.
369function cmd_version() {
370    show_version
371}
372
373"cmd_${cmd}" "$*"
374