#! /usr/bin/env mulle-bash # shellcheck shell=bash # # Copyright (c) 2018 Nat! - Mulle kybernetiK # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # Neither the name of Mulle kybernetiK nor the names of its contributors # may be used to endorse or promote products derived from this software # without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. # # Create fresh HeadersAndSources.cmake from filesystem information # [ "${TRACE}" = 'YES' ] && set -x && : "$0" "$@" MULLE_EXECUTABLE_VERSION=2.1.0 # TODO: could make this more general, so we specify a list of types # and these are then used to generate the files. usage() { if [ ! -z "$1" ] then log_error "$1" fi cat <&2 Usage: ${MULLE_USAGE_NAME} [options] Generate header and source definition files for cmake from the result of running mulle-match on the current project. The resulting file can be easily included into CMakeLists.txt with: include( cmake/_Headers.cmake OPTIONAL) include( cmake/_Sources.cmake OPTIONAL) include( cmake/_Resources.cmake OPTIONAL) A test run that writes to stdout and does not create files can be accomplished with: ${MULLE_USAGE_NAME} --stdout Options: -- --filter-type : the patternfile to use (source) --headers-file : header definitions file to create (cmake/_Headers.cmake) --serial : process headers and sources serially --sources-file : source definitions file to create (cmake/_Sources.cmake) Environment: MULLE_MATCH_TO_CMAKE_SOURCE_PATTERNFILETYPE : the patternfile type to use (source) MULLE_MATCH_TO_CMAKE_HEADER_PATTERNFILETYPE : the patternfile type to use (header) MULLE_MATCH_TO_CMAKE_RESOURCE_PATTERNFILETYPE : the patternfile type to use (resource) MULLE_MATCH_TO_CMAKE_HEADERS_FILE : headers file to create MULLE_MATCH_TO_CMAKE_SOURCES_FILE : sources file to create MULLE_MATCH_TO_CMAKE_RESOURCES_FILE : resources file to create EOF exit 1 } emit_cmake_var() { log_entry "emit_cmake_var" "$@" local name="$1" local contents="$2" local comment="$3" [ -z "${name}" ] && _internal_fail "empty name" if [ -z "${contents}" ] then echo "# No contents in ${name}" return fi if [ ! -z "${comment}" ] then echo "${comment}" fi echo "set( ${name}" LC_ALL="C" sed '/ /s/\(.*\)/"\1"/' <<< "${contents}" echo ")" echo } emit_common_directories() { log_entry "emit_common_directories" "$@" local emitter="$1" local parameter="$2" local items="$3" [ -z "${parameter}" ] && _internal_fail "parameter is empty" [ -z "${emitter}" ] && _internal_fail "emitter is empty" local collection collection="`rexekutor sed -n -e 's|^[^;]*;\(.*\)/[^/]*\.h|\1|p' <<< "${items}" | LC_ALL=C sort -u`" if [ ! -z "${collection}" ] then if [ "${PROJECT_SOURCE_DIR}" = '.' ] then r_add_line "." "${collection}" collection="${RVAL}" fi fi "${emitter}" "${parameter}" "${collection}" "# # contents are derived from the file locations " } emit_by_category() { log_entry "emit_by_category" "$@" local emitter="$1" local items="$2" local type="$3" [ -z "${emitter}" ] && _internal_fail "emitter is empty" local collectname local collection local remainder remainder="${items}" while [ ! -z "${remainder}" ] do # # https://stackoverflow.com/questions/1773939/how-to-use-sed-to-return-something-from-first-line-which-matches-and-quit-early # collectname="`sed -n -e '/^\([^;]*\).*/{s//\1/p;q;}' <<< "${remainder}" `" [ -z "${collectname}" ] && _internal_fail "garbage in passed in items: \"${remainder}\"" collection="`grep -E "^${collectname};" <<< "${remainder}" | cut -d ';' -f 2-`" r_lowercase "${collectname}" "${emitter}" "${collectname}" "${collection}" "# # contents selected with patternfile ??-${type}--${RVAL//_/-} #" remainder="`grep -E -v "^${collectname};" <<< "${remainder}" `" done : } reflect_diff() { rexekutor diff "$1" "$2" | sed -n -e 's/^>/A/p' -e 's/^&2 fi else if [ "${OPTION_DIFF}" != 'NO' ] then # fake add rexekutor grep -E -v '^#|^$|\(|\)' <<< "${text}" \ | rexekutor sed -e 's/^/A /' >&2 fi fi if ! redirect_exekutor "${filename}" printf "%s\n" "${text}" then log_error "Failed to write \"${filename}\"" return 1 fi exekutor chmod a-w "${filename}" || log_warning "Failed to protect \"${filename}\"" } MULLE_MATCH_TO_CMAKE_BLURB="\ # This file will be regenerated by \`mulle-match-to-cmake\` via # \`mulle-sde reflect\` and any edits will be lost. #" create_headers_file() { log_entry "create_headers_file" "$@" local categorized_files="$1" local text_hdr case "${MULLE_MATCH_TO_CMAKE_HEADERS_FILE}" in DISABLE*) log_verbose "Header generation disabled by MULLE_MATCH_TO_CMAKE_HEADERS_FILE=DISABLE" return ;; NONE) text_hdr="# Header generation disabled by MULLE_MATCH_TO_CMAKE_HEADERS_FILE=NONE" MULLE_MATCH_TO_CMAKE_HEADERS_FILE="" ;; *) local text_hdr1 local text_hdr2 text_hdr1="`emit_common_directories emit_cmake_var INCLUDE_DIRS "${categorized_files}" `" || return 1 text_hdr2="`emit_by_category emit_cmake_var "${categorized_files}" 'header'`" || return 1 if [ -z "${text_hdr2}" ] then text_hdr2="# no headers" fi if [ -z "${text_hdr1}" ] then text_hdr="${text_hdr2}" else if [ -z "${text_hdr2}" ] then text_hdr="${text_hdr1}" else text_hdr="${text_hdr1} ${text_hdr2}" fi fi ;; esac if [ "${MULLE_MATCH_TO_CMAKE_HEADERS_FILE}" = '-' ] then log_info "Headers" printf "%s\n" "${text_hdr}" return 0 fi MULLE_MATCH_TO_CMAKE_HEADERS_FILE="${MULLE_MATCH_TO_CMAKE_HEADERS_FILE:-cmake/reflect/_Headers.cmake}" text_hdr="${MULLE_MATCH_TO_CMAKE_BLURB} # This file will be included by cmake/share/Headers.cmake # if( MULLE_TRACE_INCLUDE) MESSAGE( STATUS \"# Include \\\"\${CMAKE_CURRENT_LIST_FILE}\\\"\" ) endif() ${text_hdr} " log_fluff "Create cmake header files (${PWD})" output_file "${MULLE_MATCH_TO_CMAKE_HEADERS_FILE}" "${text_hdr}" } create_sources_file() { log_entry "create_sources_file" "$@" local categorized_files="$1" local text_src case "${MULLE_MATCH_TO_CMAKE_SOURCES_FILE}" in DISABLE*) log_verbose "Source generation disabled by MULLE_MATCH_TO_CMAKE_SOURCES_FILE=DISABLE" return ;; NONE) text_src="# Source generation disabled by MULLE_MATCH_TO_CMAKE_SOURCES_FILE=NONE" MULLE_MATCH_TO_CMAKE_SOURCES_FILE="" ;; *) text_src="`emit_by_category emit_cmake_var "${categorized_files}" 'source'`" || return 1 if [ -z "${text_src}" ] then # # header only libraries are tricky to do portably # text_src="message( FATAL_ERROR \" No sources found. \ \`mulle-sde environment set MULLE_MATCH_TO_CMAKE_SOURCES_FILE NONE\`, \ if this is intentional\")" fi ;; esac if [ "${MULLE_MATCH_TO_CMAKE_SOURCES_FILE}" = '-' ] then log_info "Sources" printf "%s\n" "${text_src}" return 0 fi MULLE_MATCH_TO_CMAKE_SOURCES_FILE="${MULLE_MATCH_TO_CMAKE_SOURCES_FILE:-cmake/reflect/_Sources.cmake}" text_src="${MULLE_MATCH_TO_CMAKE_BLURB} # This file will be included by cmake/share/sources.cmake # if( MULLE_TRACE_INCLUDE) MESSAGE( STATUS \"# Include \\\"\${CMAKE_CURRENT_LIST_FILE}\\\"\" ) endif() ${text_src}" output_file "${MULLE_MATCH_TO_CMAKE_SOURCES_FILE}" "${text_src}" } create_resources_file() { log_entry "create_resources_file" "$@" local categorized_files="$1" local text_src case "${MULLE_MATCH_TO_CMAKE_RESOURCES_FILE}" in DISABLE*) log_verbose "Resource generation disabled by MULLE_MATCH_TO_CMAKE_RESOURCES_FILE=DISABLE" return ;; NONE) text_src="# Source generation disabled by MULLE_MATCH_TO_CMAKE_RESOURCES_FILE=NONE" MULLE_MATCH_TO_CMAKE_RESOURCES_FILE="" ;; *) text_src="`emit_by_category emit_cmake_var "${categorized_files}" 'resource'`" || return 1 if [ -z "${text_src}" ] then # # header only libraries are tricky to do portably # text_src="message( STATUS \"No resources found. \ \`mulle-sde environment set MULLE_MATCH_TO_CMAKE_RESOURCES_FILE NONE\`, \ to avoid this (harmless) warning\")" fi ;; esac if [ "${MULLE_MATCH_TO_CMAKE_RESOURCES_FILE}" = '-' ] then log_info "Resources" printf "%s\n" "${text_src}" return 0 fi MULLE_MATCH_TO_CMAKE_RESOURCES_FILE="${MULLE_MATCH_TO_CMAKE_RESOURCES_FILE:-cmake/reflect/_Resources.cmake}" text_src="${MULLE_MATCH_TO_CMAKE_BLURB} # This file will be included by cmake/share/resources.cmake # if( MULLE_TRACE_INCLUDE) MESSAGE( STATUS \"# Include \\\"\${CMAKE_CURRENT_LIST_FILE}\\\"\" ) endif() ${text_src}" output_file "${MULLE_MATCH_TO_CMAKE_RESOURCES_FILE}" "${text_src}" } main() { log_entry "main" "$@" local OPTION_PARALLEL='YES' local OPTION_HEADERS='YES' local OPTION_SOURCES='YES' local OPTION_RESOURCES='YES' local OPTION_DIFF='DEFAULT' if [ "${MULLE_MATCH_TO_CMAKE_SOURCE_PATTERNFILETYPE}" != 'NONE' ] then MULLE_MATCH_TO_CMAKE_SOURCE_PATTERNFILETYPE="source" fi if [ "${MULLE_MATCH_TO_CMAKE_HEADER_PATTERNFILETYPE}" != 'NONE' ] then MULLE_MATCH_TO_CMAKE_HEADER_PATTERNFILETYPE="header" fi if [ "${MULLE_MATCH_TO_CMAKE_RESOURCE_PATTERNFILETYPE}" != 'NONE' ] then MULLE_MATCH_TO_CMAKE_RESOURCE_PATTERNFILETYPE="resource" fi while [ $# -ne 0 ] do if options_technical_flags "$1" then shift continue fi case "$1" in -h*|--help|help) usage ;; --parallel|--no-serial) OPTION_PARALLEL='YES' ;; --serial|--no-parallel) OPTION_PARALLEL='NO' ;; --diff) OPTION_DIFF='YES' ;; --no-diff) OPTION_DIFF='NO' ;; --no-headers) OPTION_HEADERS='NO' ;; --no-sources) OPTION_SOURCES='NO' ;; --no-resources) OPTION_RESOURCES='NO' ;; -H|--headers-file) [ $# -eq 1 ] && usage "missing argument to $1" shift MULLE_MATCH_TO_CMAKE_HEADERS_FILE="$1" ;; -S|--sources-file) [ $# -eq 1 ] && usage "missing argument to $1" shift MULLE_MATCH_TO_CMAKE_SOURCES_FILE="$1" ;; -R|--resources-file) [ $# -eq 1 ] && usage "missing argument to $1" shift MULLE_MATCH_TO_CMAKE_RESOURCES_FILE="$1" ;; --header-filter-type) [ $# -eq 1 ] && usage "missing argument to $1" shift MULLE_MATCH_TO_CMAKE_HEADER_PATTERNFILETYPE="$1" ;; -F|--source-filter-type|--filter-type) [ $# -eq 1 ] && usage "missing argument to $1" shift MULLE_MATCH_TO_CMAKE_SOURCE_PATTERNFILETYPE="$1" ;; --resource-filter-type) [ $# -eq 1 ] && usage "missing argument to $1" shift MULLE_MATCH_TO_CMAKE_RESOURCE_PATTERNFILETYPE="$1" ;; --stdout) OPTION_PARALLEL='NO' MULLE_MATCH_TO_CMAKE_SOURCES_FILE="-" MULLE_MATCH_TO_CMAKE_HEADERS_FILE="-" MULLE_MATCH_TO_CMAKE_RESOURCES_FILE="-" ;; --version) printf "%s\n" "${MULLE_EXECUTABLE_VERSION}" return 0 ;; -*) usage "Unknown option \"$1\"" ;; *) break ;; esac shift done options_setup_trace "${MULLE_TRACE}" && set -x [ $# -ne 0 ] && usage "superflous arguments $*" MULLE_MATCH="${MULLE_MATCH:-`command -v mulle-match`}" [ -z "${MULLE_MATCH}" ] && fail "mulle-match not in PATH" # in terse mode don't diff if [ "${MULLE_FLAG_LOG_TERSE}" = 'YES' -a "${OPTION_DIFF}" = 'DEFAULT' ] then OPTION_DIFF='NO' fi # # With 1000 header and 1000 source files, a full update takes ~7s on # my machine. It certainly would be interesting to make this properly # incremental. # local categorized_sources if [ "${OPTION_SOURCES}" = 'YES' ] then if ! categorized_sources="`rexekutor "${MULLE_MATCH}" \ ${MULLE_TECHNICAL_FLAGS} \ find --format "%C;%f\\n" \ --qualifier "TYPE_MATCHES ${MULLE_MATCH_TO_CMAKE_SOURCE_PATTERNFILETYPE}"`" then return 1 fi if [ -z "${categorized_sources}" ] then if [ "${MULLE_MATCH_TO_CMAKE_SOURCES_FILE}" != "NONE" ] then log_warning "No matching source files found. " >&2 fi # exit 0 # but still create empty files, otherwise cmake is unhappy fi categorized_sources="`LC_ALL=C rexekutor sort -d -t';' -k 1,2 <<< "${categorized_sources}" `" log_setting "categorized_sources=${categorized_sources}" fi # local categorized_resources if [ "${OPTION_RESOURCES}" = 'YES' ] then if ! categorized_resources="`rexekutor "${MULLE_MATCH}" \ ${MULLE_TECHNICAL_FLAGS} \ find --format "%C;%f\\n" \ --qualifier "TYPE_MATCHES ${MULLE_MATCH_TO_CMAKE_RESOURCE_PATTERNFILETYPE}"`" then return 1 fi if [ -z "${categorized_resources}" ] then log_verbose "No matching resource files found. " # exit 0 # but still create empty files, otherwise cmake is unhappy fi categorized_resources="`LC_ALL=C rexekutor sort -d -t';' -k 1,2 <<< "${categorized_resources}" `" log_setting "categorized_resources=${categorized_resources}" fi local categorized_headers if [ "${OPTION_HEADERS}" = 'YES' ] then if ! categorized_headers="`rexekutor "${MULLE_MATCH}" \ ${MULLE_TECHNICAL_FLAGS} \ find --format "%C;%f\\n" \ --qualifier "TYPE_MATCHES ${MULLE_MATCH_TO_CMAKE_HEADER_PATTERNFILETYPE}"`" then return 1 fi categorized_headers="`LC_ALL=C rexekutor sort -d -t';' -k 1,2 <<< "${categorized_headers}" `" if [ "${MULLE_FLAG_LOG_SETTINGS}" = 'YES' ] then log_setting "categorized_headers=${categorized_headers}" fi fi if [ "${OPTION_PARALLEL}" = 'YES' ] then if [ "${OPTION_HEADERS}" = 'YES' ] then create_headers_file "${categorized_headers}" & fi if [ "${OPTION_SOURCES}" = 'YES' ] then create_sources_file "${categorized_sources}" & fi if [ "${OPTION_RESOURCES}" = 'YES' ] then create_resources_file "${categorized_resources}" & fi log_fluff "waiting..." wait log_fluff 'done!' else if [ "${OPTION_HEADERS}" = 'YES' ] then create_headers_file "${categorized_headers}" fi if [ "${OPTION_SOURCES}" = 'YES' ] then create_sources_file "${categorized_sources}" fi if [ "${OPTION_RESOURCES}" = 'YES' ] then create_resources_file "${categorized_resources}" fi fi } call_with_flags "main" "${MULLE_MATCH_TO_CMAKE_FLAGS}" "$@"