#!/usr/bin/env bash set -e # Locale can change behavior of utilities like 'sort' but we want the output # to be stable on all machines. Force the locale to 'C' for consistency. export LC_ALL=C function show_usage() { cat \ << EOF Usage: $(basename "$0") [options] * Generate meson.build files from a directory tree containing YAML files and facilitate building the sdbus++ sources. Options: --help - Display this message --command - Command mode to execute (default 'meson'). --directory - Root directory of the YAML source (default '.'). --output - Root directory of the output (default '.'). --tool - Path to the processing tool (default 'sdbus++'). --version - Display this tool's version string. Commands: meson - Generate a tree of meson.build files corresponding to the source YAML files. cpp - Generate the source files from a YAML interface. markdown - Generate the markdown files from a YAML interface. version - Display this tool's version string. EOF } ## The version is somewhat arbitrary but is used to create a warning message ## if a repository contains old copies of the generated meson.build files and ## needs an update. We should increment the version number whenever the ## resulting meson.build would change. tool_version="sdbus++-gen-meson version 7" function show_version() { echo "${tool_version}" } # Set up defaults. sdbuspp="sdbus++" outputdir="." cmd="meson" rootdir="." # Parse options. options="$(getopt -o hc:d:o:t:v --long help,command:,directory:,output:,tool:,version -- "$@")" eval set -- "${options}" while true; do case "$1" in -h | --help) show_usage exit ;; -c | --command) shift cmd="$1" shift ;; -d | --directory) shift rootdir="$1" shift ;; -o | --output) shift outputdir="$1" shift ;; -t | --tool) shift sdbuspp="$1" shift ;; -v | --version) show_version exit ;; --) shift break ;; *) echo "Invalid argument $1" exit 1 ;; esac done ## Create an initially empty meson.build file. ## $1 - path to create meson.build at. function meson_empty_file() { mkdir -p "$1" echo "# Generated file; do not modify." > "$1/meson.build" } ## Create the root-level meson.build ## ## Inserts rules to run the available version of this tool to ensure the ## version has not changed. function meson_create_root() { meson_empty_file "${outputdir}" cat >> "${outputdir}/meson.build" \ << EOF sdbuspp_gen_meson_ver = run_command( sdbuspp_gen_meson_prog, '--version', check: true, ).stdout().strip().split('\n')[0] if sdbuspp_gen_meson_ver != '${tool_version}' warning('Generated meson files from wrong version of sdbus++-gen-meson.') warning( 'Expected "${tool_version}", got:', sdbuspp_gen_meson_ver ) endif EOF } ## hash-tables to store: ## meson_paths - list of subdirectory paths for which an empty meson.build ## has already been created. ## interfaces - list of interface paths which a YAML has been found and ## which YAML types (interface, errors, etc.). declare -A meson_paths declare -A interfaces ## Ensure the meson.build files to a path have been created. ## $1 - The path requiring to be created. function meson_create_path() { meson_path="${outputdir}" prev_meson_path="" # Split the path into segments. for part in $(echo "$1" | tr '/' '\n'); do prev_meson_path="${meson_path}" meson_path="${meson_path}/${part}" # Create the meson.build for this segment if it doesn't already exist. if [[ "" == "${meson_paths[${meson_path}]}" ]]; then meson_paths["${meson_path}"]="1" meson_empty_file "${meson_path}" # Add the 'subdir' link into the parent's meson.build. # We need to skip adding the links into the 'root' meson.build # because most repositories want to selectively add TLDs based # on config flags. Let them figure out their own logic for that. if [[ ${outputdir} != "${prev_meson_path}" ]]; then echo "subdir('${part}')" >> "${prev_meson_path}/meson.build" fi fi done } ## Generate the meson target for the source files (.cpp/.hpp) from a YAML ## interface. ## ## $1 - The interface to generate a target for. function meson_cpp_target() { mesondir="${outputdir}/$1" yamldir="$(realpath --relative-to="${mesondir}" "${rootdir}")" # Determine the source and output files based on the YAMLs present. sources="" outputs="" for s in ${interfaces[$1]}; do sources="'${yamldir}/$1.${s}', " case "${s}" in errors.yaml) outputs="${outputs}'error.cpp', 'error.hpp', " ;; interface.yaml) outputs="${outputs}'common.hpp', " outputs="${outputs}'server.cpp', 'server.hpp', " outputs="${outputs}'aserver.hpp', " outputs="${outputs}'client.hpp', " ;; *) echo "Unknown interface type: ${s}" exit 1 ;; esac done # Create the target to generate the 'outputs'. cat >> "${mesondir}/meson.build" \ << EOF generated_sources += custom_target( '$1__cpp'.underscorify(), input: [ ${sources} ], output: [ ${outputs} ], depend_files: sdbusplusplus_depfiles, command: [ sdbuspp_gen_meson_prog, '--command', 'cpp', '--output', meson.current_build_dir(), '--tool', sdbusplusplus_prog, '--directory', meson.current_source_dir() / '${yamldir}', '$1', ], ) EOF } ## Generate the meson target for the markdown files from a YAML interface. ## $1 - The interface to generate a target for. function meson_md_target() { mesondir="${outputdir}/$(dirname "$1")" yamldir="$(realpath --relative-to="${mesondir}" "${rootdir}")" # Determine the source files based on the YAMLs present. sources="" for s in ${interfaces[$1]}; do sources="'${yamldir}/$1.${s}', " done # Create the target to generate the interface.md file. cat >> "${mesondir}/meson.build" \ << EOF generated_others += custom_target( '$1__markdown'.underscorify(), input: [ ${sources} ], output: [ '$(basename "$1").md' ], depend_files: sdbusplusplus_depfiles, command: [ sdbuspp_gen_meson_prog, '--command', 'markdown', '--output', meson.current_build_dir(), '--tool', sdbusplusplus_prog, '--directory', meson.current_source_dir() / '${yamldir}', '$1', ], ) EOF } ## Handle command=meson by generating the tree of meson.build files. function cmd_meson() { # Find and sort all the YAML files yamls="$(find "${rootdir}" -name '*.interface.yaml' -o -name '*.errors.yaml')" yamls="$(echo "${yamls}" | sort)" # Assign the YAML files into the hash-table by interface name. for y in ${yamls}; do rel="$(realpath "--relative-to=${rootdir}" "${y}")" dir="$(dirname "${rel}")" ext="${rel#*.}" base="$(basename "${rel}" ".${ext}")" key="${dir}/${base}" interfaces["${key}"]="${interfaces[${key}]} ${ext}" done # Create the meson.build files. meson_create_root # shellcheck disable=SC2312 sorted_ifaces="$(echo "${!interfaces[@]}" | tr " " "\n" | sort)" for i in ${sorted_ifaces}; do meson_create_path "${i}" meson_cpp_target "${i}" meson_md_target "${i}" done } ## Handle command=cpp by calling sdbus++ as appropriate. ## $1 - interface to generate. ## ## For an interface foo/bar, the outputdir is expected to be foo/bar. function cmd_cpp() { if [[ "" == "$1" ]]; then show_usage exit 1 fi if [[ ! -e "${rootdir}/$1.interface.yaml" ]] && [[ ! -e "${rootdir}/$1.errors.yaml" ]]; then echo "Missing YAML for $1." exit 1 fi mkdir -p "${outputdir}" sdbusppcmd="${sdbuspp} -r ${rootdir}" intf="${1//\//.}" if [[ -e "${rootdir}/$1.interface.yaml" ]]; then ${sdbusppcmd} interface common-header "${intf}" > "${outputdir}/common.hpp" ${sdbusppcmd} interface server-header "${intf}" > "${outputdir}/server.hpp" ${sdbusppcmd} interface server-cpp "${intf}" > "${outputdir}/server.cpp" ${sdbusppcmd} interface client-header "${intf}" > "${outputdir}/client.hpp" ${sdbusppcmd} interface aserver-header "${intf}" > "${outputdir}/aserver.hpp" fi if [[ -e "${rootdir}/$1.errors.yaml" ]]; then ${sdbusppcmd} error exception-header "${intf}" > "${outputdir}/error.hpp" ${sdbusppcmd} error exception-cpp "${intf}" > "${outputdir}/error.cpp" fi } ## Handle command=markdown by calling sdbus++ as appropriate. ## $1 - interface to generate. ## ## For an interface foo/bar, the outputdir is expected to be foo. function cmd_markdown() { if [[ "" == "$1" ]]; then show_usage exit 1 fi if [[ ! -e "${rootdir}/$1.interface.yaml" ]] && [[ ! -e "${rootdir}/$1.errors.yaml" ]]; then echo "Missing YAML for $1." exit 1 fi mkdir -p "${outputdir}" sdbusppcmd="${sdbuspp} -r ${rootdir}" intf="${1//\//.}" base="$(basename "$1")" echo -n > "${outputdir}/${base}.md" if [[ -e "${rootdir}/$1.interface.yaml" ]]; then ${sdbusppcmd} interface markdown "${intf}" >> "${outputdir}/${base}.md" fi if [[ -e "${rootdir}/$1.errors.yaml" ]]; then ${sdbusppcmd} error markdown "${intf}" >> "${outputdir}/${base}.md" fi } ## Handle command=version. function cmd_version() { show_version } "cmd_${cmd}" "$*"