xref: /openbmc/sdbusplus/tools/sdbus++-gen-meson (revision 74eea5191eb7a37272ebfc68cc775fa6d289d7f5)
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
133inst_markdown_dir = get_option('datadir') / 'doc' / meson.project_name()
134inst_registry_dir = get_option('datadir') / 'redfish-registry' / meson.project_name()
135
136generated_sources = []
137generated_markdown = []
138generated_registry = []
139
140foreach d : yaml_selected_subdirs
141    subdir(d)
142endforeach
143
144generated_headers = []
145foreach s : generated_sources
146    foreach f : s.to_list()
147        if f.full_path().endswith('.hpp')
148            generated_headers += f
149        endif
150    endforeach
151endforeach
152
153EOF
154}
155
156## hash-tables to store:
157##      meson_paths - list of subdirectory paths for which an empty meson.build
158##                    has already been created.
159##      interfaces - list of interface paths which a YAML has been found and
160##                   which YAML types (interface, errors, etc.).
161declare -A meson_paths
162declare -A interfaces
163
164## Ensure the meson.build files to a path have been created.
165## $1 - The path requiring to be created.
166function meson_create_path() {
167
168    meson_path="${outputdir}"
169    prev_meson_path=""
170
171    # Split the path into segments.
172    for part in $(echo "$1" | tr '/' '\n'); do
173        prev_meson_path="${meson_path}"
174        meson_path="${meson_path}/${part}"
175
176        # Create the meson.build for this segment if it doesn't already exist.
177        if [[ "" == "${meson_paths[${meson_path}]}" ]]; then
178            meson_paths["${meson_path}"]="1"
179            meson_empty_file "${meson_path}"
180
181            # Add the 'subdir' link into the parent's meson.build.
182            # We need to skip adding the links into the 'root' meson.build
183            # because most repositories want to selectively add TLDs based
184            # on config flags.  Let them figure out their own logic for that.
185            if [[ ${outputdir} != "${prev_meson_path}" ]]; then
186                echo "subdir('${part}')" >> "${prev_meson_path}/meson.build"
187            fi
188        fi
189    done
190}
191
192## Add the 'sdbusplus_current_path' to the meson file.
193##
194## $1 - The path to insert into.
195##
196## This is done after the subdir processing because we need to leave
197## the meson variable in this state for the install directives.
198function meson_insert_current_path() {
199    meson_path="${outputdir}/${1}"
200    current_path="${1}"
201    cat >> "${meson_path}/meson.build" \
202        << EOF
203
204sdbusplus_current_path = '${current_path}'
205
206EOF
207}
208
209## Generate the meson target for the source files (.cpp/.hpp) from a YAML
210## interface.
211##
212## $1 - The interface to generate a target for.
213function meson_cpp_target() {
214    mesondir="${outputdir}/$1"
215    yamldir="$(realpath --relative-to="${mesondir}" "${rootdir}")"
216    nl=$'\n'
217    ind="        "
218
219    # Determine the source and output files based on the YAMLs present.
220    sources=""
221    outputs=""
222    install=""
223    for s in ${interfaces[$1]}; do
224        sources="${sources}${ind}'${yamldir}/$1.${s}',${nl}"
225
226        case "${s}" in
227            errors.yaml)
228                outputs="${outputs}${ind}'error.cpp',${nl}"
229                install="${install}${ind}false,${nl}"
230
231                outputs="${outputs}${ind}'error.hpp',${nl}"
232                install="${install}${ind}get_option('includedir') / sdbusplus_current_path,${nl}"
233                ;;
234
235            events.yaml)
236                outputs="${outputs}${ind}'event.cpp',${nl}"
237                install="${install}${ind}false,${nl}"
238
239                outputs="${outputs}${ind}'event.hpp',${nl}"
240                install="${install}${ind}get_option('includedir') / sdbusplus_current_path,${nl}"
241                ;;
242
243            interface.yaml)
244                outputs="${outputs}${ind}'common.hpp',${nl}"
245                install="${install}${ind}get_option('includedir') / sdbusplus_current_path,${nl}"
246
247                outputs="${outputs}${ind}'server.hpp',${nl}"
248                install="${install}${ind}get_option('includedir') / sdbusplus_current_path,${nl}"
249
250                outputs="${outputs}${ind}'server.cpp',${nl}"
251                install="${install}${ind}false,${nl}"
252
253                outputs="${outputs}${ind}'aserver.hpp',${nl}"
254                install="${install}${ind}get_option('includedir') / sdbusplus_current_path,${nl}"
255
256                outputs="${outputs}${ind}'client.hpp',${nl}"
257                install="${install}${ind}get_option('includedir') / sdbusplus_current_path,${nl}"
258                ;;
259
260            *)
261                echo "Unknown interface type: ${s}"
262                exit 1
263                ;;
264        esac
265    done
266
267    # Create the target to generate the 'outputs'.
268    cat >> "${mesondir}/meson.build" \
269        << EOF
270generated_sources += custom_target(
271    '$1__cpp'.underscorify(),
272    input: [
273${sources}    ],
274    output: [
275${outputs}    ],
276    depend_files: sdbusplusplus_depfiles,
277    command: [
278        sdbuspp_gen_meson_prog, '--command', 'cpp',
279        '--output', meson.current_build_dir(),
280        '--tool', sdbusplusplus_prog,
281        '--directory', meson.current_source_dir() / '${yamldir}',
282        '$1',
283    ],
284    install: should_generate_cpp,
285    install_dir: [
286${install}    ],
287    build_by_default: should_generate_cpp,
288)
289
290EOF
291}
292
293## Generate the meson target for the markdown files from a YAML interface.
294## $1 - The interface to generate a target for.
295function meson_md_target() {
296    mesondir="${outputdir}/$(dirname "$1")"
297    yamldir="$(realpath --relative-to="${mesondir}" "${rootdir}")"
298
299    # Determine the source files based on the YAMLs present.
300    sources=""
301    for s in ${interfaces[$1]}; do
302        sources="${sources}'${yamldir}/$1.${s}', "
303    done
304
305    # Create the target to generate the interface.md file.
306    cat >> "${mesondir}/meson.build" \
307        << EOF
308generated_markdown += custom_target(
309    '$1__markdown'.underscorify(),
310    input: [ ${sources} ],
311    output: [ '$(basename "$1").md' ],
312    depend_files: sdbusplusplus_depfiles,
313    command: [
314        sdbuspp_gen_meson_prog, '--command', 'markdown',
315        '--output', meson.current_build_dir(),
316        '--tool', sdbusplusplus_prog,
317        '--directory', meson.current_source_dir() / '${yamldir}',
318        '$1',
319    ],
320    install: should_generate_markdown,
321    install_dir: [inst_markdown_dir / sdbusplus_current_path],
322    build_by_default: should_generate_markdown,
323)
324
325EOF
326}
327
328## Generate the meson target for the registry files from a YAML interface.
329## $1 - The interface to generate a target for.
330function meson_registry_target() {
331    mesondir="${outputdir}/$(dirname "$1")"
332    yamldir="$(realpath --relative-to="${mesondir}" "${rootdir}")"
333
334    # Determine the source and output files based on the YAMLs present.
335    sources=""
336    outputs=""
337    for s in ${interfaces[$1]}; do
338        case "${s}" in
339            errors.yaml)
340                ;;
341
342            events.yaml)
343                sources="${sources}'${yamldir}/$1.${s}', "
344                outputs="${outputs}'event.cpp', 'event.hpp', "
345                ;;
346
347            interface.yaml)
348                ;;
349
350            *)
351                echo "Unknown interface type: ${s}"
352                exit 1
353                ;;
354        esac
355    done
356
357    if [[ -z "${sources}" ]]; then
358        return
359    fi
360
361    # Create the target to generate the interface.md file.
362    cat >> "${mesondir}/meson.build" \
363        << EOF
364generated_registry += custom_target(
365    '$1__registry'.underscorify(),
366    input: [ ${sources} ],
367    output: [ '$(basename "$1").json' ],
368    depend_files: sdbusplusplus_depfiles,
369    command: [
370        sdbuspp_gen_meson_prog, '--command', 'registry',
371        '--output', meson.current_build_dir(),
372        '--tool', sdbusplusplus_prog,
373        '--directory', meson.current_source_dir() / '${yamldir}',
374        '$1',
375    ],
376    install: should_generate_registry,
377    install_dir: [inst_registry_dir / sdbusplus_current_path],
378    build_by_default: should_generate_registry,
379)
380
381EOF
382}
383
384
385## Handle command=meson by generating the tree of meson.build files.
386function cmd_meson() {
387    # Find and sort all the YAML files
388    yamls="$(find "${rootdir}" -name '*.interface.yaml' -o -name '*.errors.yaml' -o -name '*.events.yaml')"
389    yamls="$(echo "${yamls}" | sort)"
390
391    # Assign the YAML files into the hash-table by interface name.
392    for y in ${yamls}; do
393        rel="$(realpath "--relative-to=${rootdir}" "${y}")"
394        dir="$(dirname "${rel}")"
395        ext="${rel#*.}"
396        base="$(basename "${rel}" ".${ext}")"
397        key="${dir}/${base}"
398
399        interfaces["${key}"]="${interfaces[${key}]} ${ext}"
400    done
401
402    # Create the meson.build files.
403    meson_create_root
404    # shellcheck disable=SC2312
405    sorted_ifaces="$(echo "${!interfaces[@]}" | tr " " "\n" | sort)"
406    iface_parent_dirs="$(echo "${sorted_ifaces}" | xargs -n1 dirname)"
407    # shellcheck disable=SC2312
408    sorted_dirs="$(echo "${sorted_ifaces} ${iface_parent_dirs}" | tr " " "\n" | sort | uniq)"
409    for i in ${sorted_ifaces}; do
410        meson_create_path "${i}"
411    done
412    for i in ${sorted_dirs}; do
413        meson_insert_current_path "${i}"
414    done
415    for i in ${sorted_ifaces}; do
416        meson_cpp_target "${i}"
417        meson_md_target "${i}"
418        meson_registry_target "${i}"
419    done
420}
421
422## Handle command=cpp by calling sdbus++ as appropriate.
423## $1 - interface to generate.
424##
425## For an interface foo/bar, the outputdir is expected to be foo/bar.
426function cmd_cpp() {
427
428    if [[ "" == "$1" ]]; then
429        show_usage
430        exit 1
431    fi
432
433    if [[ ! -e "${rootdir}/$1.interface.yaml" ]] &&
434    [[ ! -e "${rootdir}/$1.errors.yaml" ]] &&
435    [[ ! -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
445    if [[ -e "${rootdir}/$1.interface.yaml" ]]; then
446        ${sdbusppcmd} interface common-header "${intf}" > "${outputdir}/common.hpp"
447        ${sdbusppcmd} interface server-header "${intf}" > "${outputdir}/server.hpp"
448        ${sdbusppcmd} interface server-cpp "${intf}" > "${outputdir}/server.cpp"
449        ${sdbusppcmd} interface client-header "${intf}" > "${outputdir}/client.hpp"
450        ${sdbusppcmd} interface aserver-header "${intf}" > "${outputdir}/aserver.hpp"
451    fi
452
453    if [[ -e "${rootdir}/$1.errors.yaml" ]]; then
454        ${sdbusppcmd} error exception-header "${intf}" > "${outputdir}/error.hpp"
455        ${sdbusppcmd} error exception-cpp "${intf}" > "${outputdir}/error.cpp"
456    fi
457
458    if [[ -e "${rootdir}/$1.events.yaml" ]]; then
459        ${sdbusppcmd} event exception-header "${intf}" > "${outputdir}/event.hpp"
460        ${sdbusppcmd} event exception-cpp "${intf}" > "${outputdir}/event.cpp"
461    fi
462}
463
464## Handle command=markdown by calling sdbus++ as appropriate.
465## $1 - interface to generate.
466##
467## For an interface foo/bar, the outputdir is expected to be foo.
468function cmd_markdown() {
469
470    if [[ "" == "$1" ]]; then
471        show_usage
472        exit 1
473    fi
474
475    if [[ ! -e "${rootdir}/$1.interface.yaml" ]] &&
476    [[ ! -e "${rootdir}/$1.errors.yaml" ]] &&
477    [[ ! -e "${rootdir}/$1.events.yaml" ]]; then
478        echo "Missing YAML for $1."
479        exit 1
480    fi
481
482    mkdir -p "${outputdir}"
483
484    sdbusppcmd="${sdbuspp} -r ${rootdir}"
485    intf="${1//\//.}"
486    base="$(basename "$1")"
487
488    echo -n > "${outputdir}/${base}.md"
489    if [[ -e "${rootdir}/$1.interface.yaml" ]]; then
490        ${sdbusppcmd} interface markdown "${intf}" >> "${outputdir}/${base}.md"
491    fi
492
493    if [[ -e "${rootdir}/$1.errors.yaml" ]]; then
494        ${sdbusppcmd} error markdown "${intf}" >> "${outputdir}/${base}.md"
495    fi
496
497    if [[ -e "${rootdir}/$1.events.yaml" ]]; then
498        ${sdbusppcmd} event markdown "${intf}" >> "${outputdir}/${base}.md"
499    fi
500}
501
502## Handle command=registry by calling sdbus++ as appropriate.
503## $1 - interface to generate.
504##
505## For an interface foo/bar, the outputdir is expected to be foo.
506function cmd_registry() {
507
508    if [[ "" == "$1" ]]; then
509        show_usage
510        exit 1
511    fi
512
513    if [[ ! -e "${rootdir}/$1.events.yaml" ]]; then
514        echo "Missing YAML for $1."
515        exit 1
516    fi
517
518    mkdir -p "${outputdir}"
519
520    sdbusppcmd="${sdbuspp} -r ${rootdir}"
521    intf="${1//\//.}"
522    base="$(basename "$1")"
523
524    if [[ -e "${rootdir}/$1.events.yaml" ]]; then
525        ${sdbusppcmd} event exception-registry "${intf}" > "${outputdir}/${base}.json"
526    fi
527}
528
529
530## Handle command=version.
531function cmd_version() {
532    show_version
533}
534
535"cmd_${cmd}" "$*"
536