#!/bin/sh
#
#  BLIS
#  An object-based framework for developing high-performance BLAS-like
#  libraries.
#
#  Copyright (C) 2014, The University of Texas at Austin
#
#  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(s) of the copyright holder(s) 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.
#
#

#
# gen-make-frag.sh
#
# Field G. Van Zee
#


print_usage()
{
	#local script_name

	# Get the script name
	#script_name=${0##*/}

	# Echo usage info
	echo " "
	echo " "$script_name
	echo " "
	echo " Field G. Van Zee"
	echo " "
	echo " Automatically generates makefile fragments for a specified directory"
	echo " tree. "
	echo " "
	echo " Usage:"
	echo "   ${script_name} [options] root_dir frag_dir templ.mk suff_list ign_list"
	echo " "
	echo " Arguments (mandatory):"
	echo " "
	echo "   root_dir    The root directory to scan when generating makefile"
	echo "               fragments."
	echo " "
	echo "   frag_dir    The root directory in which makefile fragments will be"
	echo "               generated."
	echo " "
	echo "   templ.mk    The template makefile fragment used to generate the actual"
	echo "               fragments."
	echo " "
	echo "   suff_list   File containing a newline-separated list of file suffixes"
	echo "               of source files to that the top-level makefile expects to"
	echo "               access."
	echo " "
	echo "   ign_list    File containing a newline-separated list of directory names"
	echo "               to ignore when descending recursively into "
	echo " "
	echo " The following options are accepted:"
	echo " "
	echo "   -d          dry-run"
	echo "                 Go through all the motions, but don't actually generate any"
	echo "                 makefile fragments."
	echo "   -r          recursive"
	echo "                 Also generate makefile fragments for subdirectories of"
	echo "                 root_dir."
	echo "   -h          hide"
	echo "                 Hide the makefile fragments by prepending filenames with '.'."
	echo "   -p PREFIX   prefix name"
	echo "                 Use PREFIX instead of uppercased root_dir in the makefile"
	echo "                 variable name. If the root_dir were 'stuff' and -p was not"
	echo "                 used, then source would be accumulated into a makefile"
	echo "                 variable named 'MK_STUFF', but if -p JUNK were given, then"
	echo "                 the variable name would instead be MK_JUNK."
	echo "   -o SCRIPT   output script name"
	echo "                 Use SCRIPT when outputting messages instead of the script's"
	echo "                 actual name."
	echo "   -v [0|1|2]  verboseness level"
	echo "                 level 0: silent  (no output)"
	echo "                 level 1: default (one line per directory)"
	echo "                 level 2: verbose (several lines per directory)."
	echo " "

	# Exit with non-zero exit status
	exit 1
}







#
# gen_mkfile()
#
# Creates a single makefile fragment in a user-specified directory and adds
# any local source files found to a top-level Makefile variable.
#
gen_mkfile()
{
	# Local variable declarations
	local mkfile_frag_var_name
	local this_dir
	local this_frag_dir
	local mkfile_frag_tmpl_name
	local mkfile_name
	local mkfile_frag_path
	local cur_frag_dir
	local cur_frag_path
	local local_src_files
	local sub_items
	local item_path
	local item_suffix
	local cur_frag_sub_dirs


	# Extract our arguments to local variables
	mkfile_frag_var_name=$1
	this_dir=$2
	this_frag_dir=$3


	# Make sure the target directory exists
	mkdir -p $this_frag_dir


	# Strip the leading path from the template makefile path to get its
	# simple filename. Hide the output makefile fragment filename, if
	# requested.
	mkfile_frag_tmpl_name=${mkfile_frag_tmpl_path##*/}
	if [ -n "$hide_flag" ]; then
		mkfile_frag_path=$this_frag_dir/.$mkfile_frag_tmpl_name
	else
		mkfile_frag_path=$this_frag_dir/$mkfile_frag_tmpl_name
	fi


	# Determine the directory in which the fragment will reside.
	cur_frag_path=$this_dir
	cur_frag_dir=${this_dir##*/}


	# Initialize the local source list to empty
	local_src_files=""

	# Get a listing of the items in $this_dir
	sub_items=$(ls $this_dir)

	# Generate a list of the source files we've chosen
	for item in $sub_items; do

		# Prepend the directory to the item to get a relative path
		item_path=$this_dir/$item

		# Acquire the item's suffix, if it has one
		item_suffix=${item_path##*.}

		# If the suffix matches, then add it to our list
		if is_in_list $item_suffix "$src_file_suffixes"
		then
			local_src_files="$local_src_files $item"
		fi
	done

	# Delete the leading " " space character in the local source files list.
	local_src_files=${local_src_files##" "}


	# Initialize the fragment subdirectory list to empty
	cur_frag_sub_dirs=""

	# Capture the relative path listing of items in $this_dir.
	sub_items=$(ls $this_dir)

	# Determine the fragment's subdirectory names, if any exist
	for item in $sub_items; do

		# Prepend the directory to the item to get a relative path
		item_path=$this_dir/$item

		# If item is a directory, and it's not in the ignore list, descend into it.
		#if [ -d $item_path ] && ! should_ignore $item; then
		if [ "$recursive_flag" = "1" ] && [ -d $item_path ] && ! is_in_list $item "$ignore_dirs" ; then
			cur_frag_sub_dirs=$cur_frag_sub_dirs" "$item
		fi
	done

	# Delete the leading " " space character in fragment's subdirectory list.
	cur_frag_sub_dirs=${cur_frag_sub_dirs##" "}


	# Be verbose, if level 2 was requested.
	if [ "$verbose_flag" = "2" ]; then
		echo "mkf frag tmpl path: $mkfile_frag_tmpl_path"
		echo "mkf frag path:      $mkfile_frag_path"
		echo "cur frag path:      $cur_frag_path"
		echo "cur frag dir:       $cur_frag_dir"
		echo "cur frag sub dirs:  $cur_frag_sub_dirs"
		echo "local src files:    $local_src_files"
		echo "src file suffixes:  $src_file_suffixes"
		echo "mkf frag var name:  $mkfile_frag_var_name"
		echo "--------------------------------------------------"
	fi


	# Copy the template makefile to the directory given, using the new
	# makefile name we just created above.
	if [ -z "$dry_run_flag" ]; then
		cat $mkfile_frag_tmpl_path | sed -e s/"$mkfile_fragment_cur_dir_name_anchor"/"$cur_frag_dir"/g \
		                           | sed -e s/"$mkfile_fragment_sub_dir_names_anchor"/"$cur_frag_sub_dirs"/g \
		                           | sed -e s/"$mkfile_fragment_local_src_files_anchor"/"$local_src_files"/g \
		                           | sed -e s/"$mkfile_fragment_src_var_name_anchor"/"$mkfile_frag_var_name"/g \
		                           > $mkfile_frag_path
	fi


	# Return peacefully.
	return 0
}


#
# gen_mkfiles
#
# Recursively generates makefile fragments for a directory and all
# subdirectories. All of the actual work happens in gen_mkfile().
#
gen_mkfiles()
{
	# Local variable declarations
	local item sub_items cur_dir this_frag_dir this_dir


	# Extract our argument
	cur_dir=$1
	this_frag_dir=$2


	# Append a relevant suffix to the makefile variable name, if necesary
	# NOTE: This step is disabled because special directories are presently
	# ignored when generating makefile variable names.
	#all_add_src_var_name "$cur_dir"


	# Be verbose if level 2 was requested
	if   [ "$verbose_flag" = "2" ]; then
		echo ">>>" $script_name ${src_var_name}_$SRC $cur_dir $this_frag_dir
	elif [ "$verbose_flag" = "1" ]; then
		echo "$script_name: creating makefile fragment in $this_frag_dir from $cur_dir"
	fi


	# Call our function to generate a makefile in the directory given.
	gen_mkfile "${src_var_name}_$SRC" $cur_dir $this_frag_dir


	# Get a listing of the directories in $directory
	sub_items=$(ls $cur_dir)

	# Descend into the contents of root_dir to generate the subdirectories'
	# makefile fragments.
	for item in $sub_items; do

		# If item is a directory, and it's not in the ignore list, descend into it.
		#if [ -d "$cur_dir/$item" ] && ! should_ignore $item; then
		if [ -d "$cur_dir/$item" ] && ! is_in_list $item "$ignore_dirs" ; then
			gen_mkfiles $cur_dir/$item $this_frag_dir/$item
		fi
	done


	# Remove a relevant suffix from the makefile variable name, if necesary
	# NOTE: This step is disabled because special directories are presently
	# ignored when generating makefile variable names.
	#all_del_src_var_name "$cur_dir"


	# Return peacefully
	return 0
}



#update_src_var_name_special()
#{
#	local dir act i name var_suffix
#
#	# Extract arguments.
#	act="$1"
#	dir="$2"
#
#	# Strip / from end of directory path, if there is one, and then strip
#	# path from directory name.
#	dir=${dir%/}
#	dir=${dir##*/}
#
#	# Run through our list.
#	# NOTE: CURRENTLY, SPECIAL DIRECTORY NAMES ARE IGNORED. In order to
#	#       re-enable them, remove the quotes from "${special_dirs}".
#	for specdir in "${special_dirs}"; do
#
#		# If the current item matches sdir, then we'll have
#		# to make a modification of some form.
#		if [ "$dir" = "$specdir" ]; then
#
#			# Convert the directory name to uppercase.
#			var_suffix=$(echo "$dir" | tr '[:lower:]' '[:upper:]')
#
#			# Either add or remove the suffix, and also update the
#			# source file suffix variable.
#			if [ "$act" == "+" ]; then
#				src_var_name=${src_var_name}_$var_suffix
#			else
#				src_var_name=${src_var_name%_$var_suffix}
#			fi
#
#			# No need to continue iterating.
#			break;
#		fi
#	done
#}

#init_src_var_name()
#{
#	local dir="$1"
#
#	# Strip off the leading / if there is one
#	dir=${dir%%/}
#
#	# Convert the / directory separators into spaces to make a list of
#	# directories.
#	list=${dir//\// }
#
#	# Inspect each item in $list
#	for item in $list; do
#
#		# Try to initialize the source variable name
#		all_add_src_var_name $item
#	done
#}

#all_add_src_var_name()
#{
#	local dir="$1"
#
#	update_src_var_name_special "+" "$dir"
#
#}

#all_del_src_var_name()
#{
#	local dir="$1"
#
#	update_src_var_name_special "-" "$dir"
#}

read_mkfile_config()
{
	# Read the file describing file suffixes.
	src_file_suffixes=$(cat "${suffix_file}")

	# Read the file listing the directories to ignore.
	ignore_dirs=$(cat "${ignore_file}")

	# Change newlines into spaces. This is optional, but helps when
	# printing these values out (so they appear on one line).
	src_file_suffixes=$(echo ${src_file_suffixes} | sed "s/\n/ /g")
	ignore_dirs=$(echo ${ignore_dirs} | sed "s/\n/ /g")

}

main()
{
	# -- BEGIN GLOBAL VARIABLE DECLARATIONS --

	# Define these makefile template "anchors" used in gen_mkfile().
	mkfile_fragment_cur_dir_name_anchor="_mkfile_fragment_cur_dir_name_"
	mkfile_fragment_sub_dir_names_anchor="_mkfile_fragment_sub_dir_names_"
	mkfile_fragment_local_src_files_anchor="_mkfile_fragment_local_src_files_"
	mkfile_fragment_src_var_name_anchor="_mkfile_fragment_src_var_name_"

	# The name of the script, stripped of any preceeding path.
	script_name=${0##*/}

	# The prefix for all makefile variables.
	src_var_name_prefix='MK'

	# The variable that always holds the string that will be passed to
	# gen_mkfile() as the source variable to insert into the fragment.mk.
	src_var_name=''

	# The suffix appended to all makefile fragment source variables.
	SRC='SRC'

	# The list of source file suffixes to add to the makefile variables.
	src_file_suffixes=''

	# The lists of directories to ignore.
	ignore_dirs=''

	# The arguments to this function. They'll get assigned meaningful
	# values after getopts.
	root_dir=""
	frag_dir=""
	mkfile_frag_tmpl_path=""
	suffix_file=""
	ignore_file=""

	# Flags set by getopts.
	dry_run_flag=""
	hide_flag=""
	recursive_flag=""
	output_name=""
	prefix_flag=""
	verbose_flag=""

	# -- END GLOBAL VARIABLE DECLARATIONS --


	# Local variable declarations.
	local item sub_items this_dir


	# Process our command line options.
	while getopts ":dho:p:rv:" opt; do
		case $opt in
			d  ) dry_run_flag="1" ;;
			h  ) hide_flag="1" ;;
			r  ) recursive_flag="1" ;;
			o  ) output_name=$OPTARG ;;
			p  ) prefix_flag=$OPTARG ;;
			v  ) verbose_flag=$OPTARG ;;
			\? ) print_usage
		esac
	done
	shift $(($OPTIND - 1))


	# Make sure that verboseness level is valid.
	if [ "$verbose_flag" != "0" ] &&
	   [ "$verbose_flag" != "1" ] &&
	   [ "$verbose_flag" != "2" ]; then
		verbose_flag="1"
	fi

	# Check the number of arguments after command line option processing.
	if [ $# != "5" ]; then
		print_usage
	fi

	# If an output script name was given, overwrite script_name with it.
	if [ -n "${output_name}" ]; then
		script_name="${output_name}"
	fi


	# Extract our arguments.
	root_dir=$1
	frag_dir=$2
	mkfile_frag_tmpl_path=$3
	suffix_file=$4
	ignore_file=$5


	# Read the makefile config files to be used in the makefile fragment
	# generation.
	read_mkfile_config


	# Strip / from end of directory path, if there is one.
	root_dir=${root_dir%/}
	frag_dir=${frag_dir%/}


	# Initialize the name of the makefile source variable.
	if [ -n "$prefix_flag" ]; then

		# If prefix_flag is not null, then we construct src_var_name using
		# it instead of root_dir. So if the prefix is 'junk', we will get
		# makefile variables that begin with 'MK_JUNK'.
		root_dir_upper=$(echo "$prefix_flag" | tr '[:lower:]' '[:upper:]')
		src_var_name="${src_var_name_prefix}_${root_dir_upper}"

	else

		# Otherwise, we use root_dir. If the root directory is 'foo' then
		# makefile variables will begin with 'MK_FOO'.
		# We are also careful to convert forward slashes into underscore so
		# root directories such as foo/bar result in makefile variables
		# that begin with 'MK_FOO_BAR'.
		root_dir_upper=$(echo "$root_dir" | tr '[:lower:]' '[:upper:]')
		root_dir_upper=$(echo "$root_dir_upper" | tr '/' '_')
		src_var_name="${src_var_name_prefix}_${root_dir_upper}"
	fi


	# Be verbose if level 2 was requested.
	if   [ "$verbose_flag" = "2" ]; then
		echo ">>>" $script_name ${src_var_name}_$SRC $root_dir $frag_dir
	elif [ "$verbose_flag" = "1" ]; then
		echo "$script_name: creating makefile fragment in $frag_dir from $root_dir"
	fi


	# Call our function to generate a makefile in the root directory given.
	gen_mkfile "${src_var_name}_$SRC" $root_dir $frag_dir


	# If we were asked to act recursively, then continue processing
	# root_dir's contents.
	if [ -n "$recursive_flag" ]; then

		# Get a listing of the directories in $directory.
		sub_items=$(ls $root_dir)

		# Descend into the contents of root_dir to generate the makefile
		# fragments.
		for item in $sub_items; do

			# If item is a directory, and it's not in the ignore list, descend into it.
			#if [ -d "$root_dir/$item" ] && ! should_ignore $item ; then
			if [ -d "$root_dir/$item" ] && ! is_in_list $item "$ignore_dirs" ; then

				gen_mkfiles $root_dir/$item $frag_dir/$item
			fi
		done
	fi


	# Exit peacefully.
	return 0
}

is_in_list()
{
	local cur_item the_item item_list

	# Extract argument.
	the_item="$1"
	item_list="$2"

	# Check each item in the list against the item of interest.
	for cur_item in ${item_list}; do

		# If the current item in the list matches the one of interest.
		if [ "${cur_item}" = "${the_item}" ]; then

			# Return success (ie: item was found).
			return 0
		fi
	done

	# If we made it this far, return failure (ie: item not found).
	return 1
}

# The script's main entry point, passing all parameters given.
main "$@"
