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