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