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