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