1#!/usr/bin/env bash
2
3#
4# gen-make-frag.sh
5#
6# Field G. Van Zee
7#
8
9
10print_usage()
11{
12	#local script_name
13
14	# Get the script name
15	#script_name=${0##*/}
16
17	# Echo usage info
18	echo " "
19	echo " "$script_name
20	echo " "
21	echo " Field G. Van Zee"
22	echo " "
23	echo " Automatically generates makefile fragments for a given directory. "
24	echo " "
25	echo " Usage:"
26	echo "   ${script_name} [options] root_dir frag_dir templ.mk"
27	echo " "
28	echo " Arguments (mandatory):"
29	echo " "
30	echo "   root_dir    The root directory to scan when generating makefile"
31	echo "               fragments."
32	echo " "
33	echo "   frag_dir    The root directory in which makefile fragments will be"
34	echo "               generated."
35	echo " "
36	echo "   templ.mk    The template makefile fragment used to generate the actual"
37	echo "               fragments."
38	echo " "
39	echo " "
40	echo " The following options are accepted:"
41	echo " "
42	echo "   -d          dry-run"
43	echo "                 Go through all the motions, but don't actually generate any"
44	echo "                 makefile fragments."
45	echo "   -r          recursive"
46	echo "                 Also generate makefile fragments for subdirectories."
47	echo "   -h          hide"
48	echo "                 Hide the makefile fragments by prepending filenames with '.'."
49	echo "   -v [0|1|2]  verboseness level"
50	echo "                 level 0: silent  (no output)"
51	echo "                 level 1: default (one line per directory)"
52	echo "                 level 2: verbose (several lines per directory)."
53	echo " "
54
55	# Exit with non-zero exit status
56	exit 1
57}
58
59
60
61
62
63
64
65#
66# gen_mkfile()
67#
68# Creates a single makefile fragment in a user-specified directory and adds
69# any local source files found to a top-level Makefile variable.
70#
71gen_mkfile()
72{
73	# Local variable declarations
74	local mkfile_frag_tmpl_path
75	local mkfile_frag_var_name
76	local src_file_suffixes
77	local this_dir
78	local this_frag_dir
79	local mkfile_frag_tmpl_name
80	local mkfile_name
81	local mkfile_frag_path
82	local curr_frag_dir
83	local curr_frag_path
84	local local_src_files
85	local sub_items
86	local item_path
87	local item_suffix
88	local curr_frag_sub_dirs
89
90
91	# Extract our arguments to local variables
92	mkfile_frag_tmpl_path=$1
93	mkfile_frag_var_name=$2
94	src_file_suffixes="$3"
95	this_dir=$4
96	this_frag_dir=$5
97
98
99	# Strip the leading path from the template makefile path to get its
100	# simple filename. Hide the output makefile fragment filename, if
101	# requested.
102	mkfile_frag_tmpl_name=${mkfile_frag_tmpl_path##*/}
103	if [ -n "$hide_flag" ]; then
104		mkfile_frag_path=$this_frag_dir/.$mkfile_frag_tmpl_name
105	else
106		mkfile_frag_path=$this_frag_dir/$mkfile_frag_tmpl_name
107	fi
108
109
110	# Determine the directory in which the fragment will reside.
111	curr_frag_path=$this_dir
112	curr_frag_dir=${this_dir##*/}
113
114
115	# Initialize the local source list to empty
116	local_src_files=""
117
118	# Get a listing of the items in $this_dir
119	sub_items=$(ls $this_dir)
120
121	# Generate a list of the source files we've chosen
122	for item in $sub_items; do
123
124		# Prepend the directory to the item to get a relative path
125		item_path=$this_dir/$item
126
127		# Acquire the item's suffix, if it has one
128		item_suffix=${item_path##*.}
129
130		# If the suffix matches, then add it to our list
131		if is_in_list $item_suffix "$src_file_suffixes"
132		then
133			local_src_files="$local_src_files $item"
134		fi
135	done
136
137	# Delete the leading " " space character in the local source files list.
138	local_src_files=${local_src_files##" "}
139
140
141	# Initialize the fragment subdirectory list to empty
142	curr_frag_sub_dirs=""
143
144	# Capture the relative path listing of items in $this_dir.
145	sub_items=$(ls $this_dir)
146
147	# Determine the fragment's subdirectory names, if any exist
148	for item in $sub_items; do
149
150		# Prepend the directory to the item to get a relative path
151		item_path=$this_dir/$item
152
153		# If item is a directory, and it's not in the ignore list, descend into it.
154		if [ -d $item_path ] && ! should_ignore $item; then
155			curr_frag_sub_dirs=$curr_frag_sub_dirs" "$item
156		fi
157	done
158
159	# Delete the leading " " space character in fragment's subdirectory list.
160	curr_frag_sub_dirs=${curr_frag_sub_dirs##" "}
161
162
163	# Be verbose, if level 2 was requested.
164	if [ "$verbose_flag" = "2" ]; then
165		echo "mkf frag tmpl path: $mkfile_frag_tmpl_path"
166		echo "mkf frag path:      $mkfile_frag_path"
167		echo "curr frag path:     $curr_frag_path"
168		echo "curr frag dir:      $curr_frag_dir"
169		echo "curr frag sub dirs: $curr_frag_sub_dirs"
170		echo "local src files:    $local_src_files"
171		echo "src file suffixes:  $src_file_suffixes"
172		echo "mkf frag var name:  $mkfile_frag_var_name"
173		echo "--------------------------------------------------"
174	fi
175
176
177	# Copy the template makefile to the directory given, using the new
178	# makefile name we just created above.
179	if [ -z "$dry_run_flag" ]; then
180		cat $mkfile_frag_tmpl_path | sed -e s/"$mkfile_fragment_curr_dir_name_anchor"/"$curr_frag_dir"/g \
181		                           | sed -e s/"$mkfile_fragment_sub_dir_names_anchor"/"$curr_frag_sub_dirs"/g \
182		                           | sed -e s/"$mkfile_fragment_local_src_files_anchor"/"$local_src_files"/g \
183		                           | sed -e s/"$mkfile_fragment_src_var_name_anchor"/"$mkfile_frag_var_name"/g \
184		                           > $mkfile_frag_path
185	fi
186
187
188	# Return peacefully.
189	return 0
190}
191
192
193#
194# gen_mkfiles
195#
196# Recursively generates makefile fragments for a directory and all
197# subdirectories. All of the actual work happens in gen_mkfile().
198#
199gen_mkfiles()
200{
201	# Local variable declarations
202	local item sub_items curr_dir this_frag_dir this_dir
203
204
205	# Extract our argument
206	curr_dir=$1
207	this_frag_dir=$2
208
209
210	# Append a relevant suffix to the makefile variable name, if necesary
211	all_add_src_var_name "$curr_dir"
212
213
214	# Be verbose if level 2 was requested
215	if   [ "$verbose_flag" = "2" ]; then
216		echo ">>>" $script_name $mkfile_frag_tmpl_path ${src_var_name}_$SRC "\"$src_file_suffixes\"" $curr_dir $this_frag_dir
217	elif [ "$verbose_flag" = "1" ]; then
218		echo "$script_name: creating makefile fragment in $this_frag_dir from $curr_dir"
219	fi
220
221
222	# Call our function to generate a makefile in the directory given.
223	gen_mkfile $mkfile_frag_tmpl_path "${src_var_name}_$SRC" "$src_file_suffixes" $curr_dir $this_frag_dir
224
225
226	# Get a listing of the directories in $directory
227	sub_items=$(ls $curr_dir)
228
229	# Descend into the contents of root_dir to generate the subdirectories'
230	# makefile fragments.
231	for item in $sub_items; do
232
233		# If item is a directory, and it's not in the ignore list, descend into it.
234		if [ -d "$curr_dir/$item" ] && ! should_ignore $item; then
235			gen_mkfiles $curr_dir/$item $this_frag_dir/$item
236		fi
237	done
238
239
240	# Remove a relevant suffix from the makefile variable name, if necesary
241	all_del_src_var_name "$curr_dir"
242
243
244	# Return peacefully
245	return 0
246}
247
248
249
250update_src_var_name_lib()
251{
252	local dir act i name var_suffix
253
254
255	# Extract arguments
256	act="$1"
257	dir="$2"
258
259
260	# Strip / from end of directory path, if there is one, and then strip
261	# path from directory name.
262	dir=${dir%/}
263	dir=${dir##*/}
264
265
266	# Run through our list
267	for i in ${lib_i[@]}; do
268
269		# Get the ith name
270		name=${lib_name[$i]}
271
272		# If the current item matches $dir, then we'll probably have to make
273		# a modification of some form to src_var_name.
274		if [ "$dir" = "$name" ]; then
275
276			# Get the suffix in uppercase.
277			var_suffix=$(echo "$name" | tr '[:lower:]' '[:upper:]')
278
279			# Either add or remove the suffix.
280			if [ "$act" == "+" ]; then
281
282				# This conditional is added so that only the first directory
283				# matching an item in the lib_list is appended to the
284				# src_var_name variable. Otherwise we might have source in
285				# base/flamec/wrappers/blas/3/gemm being appended to a
286				# variable named MK_BASE_FLAMEC_BLAS_SRC, which is not what
287				# we want.
288				if [ "$src_var_name" = "MK" ]; then
289					src_var_name=${src_var_name}_$var_suffix
290				else
291					continue
292				fi
293			else
294				src_var_name=${src_var_name%_$var_suffix}
295			fi
296
297			# No need to continue iterating.
298			break;
299		fi
300	done
301}
302update_src_var_name_leaf()
303{
304	local dir act i name var_suffix
305
306
307	# Extract arguments
308	act="$1"
309	dir="$2"
310
311
312	# Strip / from end of directory path, if there is one, and then strip
313	# path from directory name.
314	dir=${dir%/}
315	dir=${dir##*/}
316
317
318	# Run through our list
319	for i in ${leaf_i[@]}; do
320
321		# Get the ith name
322		name=${leaf_name[$i]}
323
324		# If the current item matches $dir, then we'll have
325		# to make a modification of some form.
326		if [ "$dir" = "$name" ]; then
327
328			# Convert the variable suffix to uppercase.
329			var_suffix=$(echo "$name" | tr '[:lower:]' '[:upper:]')
330
331			# Get the valid source file suffixes from the leaf array.
332			file_suffix_list=${leaf_suffix[$i]}
333
334			# Either add or remove the suffix, and also update the
335			# source file suffix variable.
336			if [ "$act" == "+" ]; then
337				src_var_name=${src_var_name}_$var_suffix
338				src_file_suffixes="$file_suffix_list"
339			else
340				src_var_name=${src_var_name%_$var_suffix}
341				src_file_suffixes=$no_file_suffix
342			fi
343
344			# No need to continue iterating.
345			break;
346		fi
347	done
348}
349
350init_src_var_name()
351{
352	local dir="$1"
353
354	# Strip off the leading / if there is one
355	dir=${dir%%/}
356
357	# Convert the / directory separators into spaces to make a list of
358	# directories.
359	list=${dir//\// }
360
361	# Inspect each item in $list
362	for item in $list; do
363
364		# Try to initialize the source variable name
365		all_add_src_var_name $item
366	done
367}
368
369all_add_src_var_name()
370{
371	local dir="$1"
372
373	update_src_var_name_lib  "+" "$dir"
374	update_src_var_name_leaf "+" "$dir"
375
376}
377
378all_del_src_var_name()
379{
380	local dir="$1"
381
382	update_src_var_name_leaf "-" "$dir"
383	update_src_var_name_lib  "-" "$dir"
384}
385
386read_mkfile_var_config()
387{
388	local index lname lsuff
389	declare -i count
390
391	# Read each line of the file describing the library types that might be
392	# built.
393	count=0
394	for i in $(cat "${script_path}/config/lib_list"); do
395
396		# Get the index and library name for each line
397		#index=${i%%:*}
398		#lname=${i##*:}
399		lname=${i}
400
401		# Save this info into their respective arrays
402		lib_i[$count]=$count
403		lib_name[$count]=$lname
404
405		# Increment the counter
406		let count=$count+1
407	done
408
409
410	# Read each line of the file describing leaf node types
411	count=0
412	for i in $(cat "${script_path}/config/leaf_list"); do
413
414		# Get the index, suffix, and directory name for each line
415		#index=${i%%:*}
416		lname=${i%%:*}
417		#lname=${lname#*:}
418		lsuff=${i##*:}
419		lsuff=${lsuff//,/ }
420
421		# Save this info into their respective arrays
422		leaf_i[$count]=$count
423		leaf_name[$count]=$lname
424		leaf_suffix[$count]=$lsuff
425
426		# Increment the counter
427		let count=$count+1
428	done
429
430
431	# Read each line of the file describing directories to ignore
432	count=0
433	for i in $(cat "${script_path}/config/ignore_list"); do
434
435		# Get the index and name for each line
436		#index=${i%%:*}
437		lname=${i}
438
439		# Save this info into their respective arrays
440		ignore_i[$count]=$count
441		ignore_name[$count]=$lname
442
443		# Increment the counter
444		let count=$count+1
445	done
446}
447
448main()
449{
450	# Global array delcarations
451	declare -a lib_i
452	declare -a lib_name
453	declare -a leaf_i
454	declare -a leaf_name
455	declare -a leaf_suffix
456	declare -a ignore_i
457	declare -a ignore_name
458
459
460	# Define these makefile template "anchors" used in gen_mkfile()
461	mkfile_fragment_curr_dir_name_anchor="_mkfile_fragment_curr_dir_name_"
462	mkfile_fragment_sub_dir_names_anchor="_mkfile_fragment_sub_dir_names_"
463	mkfile_fragment_local_src_files_anchor="_mkfile_fragment_local_src_files_"
464	mkfile_fragment_src_var_name_anchor="_mkfile_fragment_src_var_name_"
465
466	# The name of the script, stripped of any preceeding path.
467	script_name=${0##*/}
468
469	# The path to the script.
470	script_path=${0%/${script_name}}
471
472	# The variable that always holds the string that will be passed to
473	# gen_mkfile() as the source variable to insert into the fragment.mk.
474	src_var_name='MK'
475
476	# The suffix appended to all makefile fragment source variables
477	SRC='SRC'
478
479	# The placeholder we use to signify that we're not looking for any
480	# source files. (Does any file format use .z?)
481	no_file_suffix='z'
482	src_file_suffixes='z'
483
484	# The arguments to this function. They'll get assigned meaningful
485	# values after getopts.
486	root_dir=""
487	frag_dir=""
488	mkfile_frag_tmpl_path=""
489
490	# Flags set by getopts.
491	dry_run_flag=""
492	hide_flag=""
493	recursive_flag=""
494	verbose_flag=""
495
496
497	# Local variable declarations
498	local item sub_items this_dir
499
500
501
502	# Read the makefile source variable config files to be used in the
503	# makefile fragment generation.
504	read_mkfile_var_config
505
506
507	# Process our command line options.
508	while getopts ":dhrv:" opt; do
509		case $opt in
510			d  ) dry_run_flag="1" ;;
511			h  ) hide_flag="1" ;;
512			r  ) recursive_flag="1" ;;
513			v  ) verbose_flag=$OPTARG ;;
514			\? ) print_usage
515		esac
516	done
517	shift $(($OPTIND - 1))
518
519
520	# Make sure that verboseness level is valid
521	if [ "$verbose_flag" != "0" ] &&
522	   [ "$verbose_flag" != "1" ] &&
523	   [ "$verbose_flag" != "2" ]; then
524		verbose_flag="1"
525	fi
526
527	# Check the number of arguments after command line option processing.
528	if [ $# != "3" ]; then
529		print_usage
530	fi
531
532
533	# Extract our arguments
534	root_dir=$1
535	frag_dir=$2
536	mkfile_frag_tmpl_path=$3
537
538
539	# Strip / from end of directory path, if there is one.
540	root_dir=${root_dir%/}
541	frag_dir=${frag_dir%/}
542
543
544	# Append relevant suffixes to the makefile variable name based on the
545	# current root, if any of the directory names match.
546	init_src_var_name "$root_dir"
547
548
549	# Be verbose if level 2 was requested
550	if   [ "$verbose_flag" = "2" ]; then
551		echo ">>>" $script_name $mkfile_frag_tmpl_path ${src_var_name}_$SRC "\"$src_file_suffixes\"" $root_dir $frag_dir
552	elif [ "$verbose_flag" = "1" ]; then
553		echo "$script_name: creating makefile fragment in $frag_dir from $root_dir"
554	fi
555
556
557	# Call our function to generate a makefile in the root directory given.
558	gen_mkfile $mkfile_frag_tmpl_path "${src_var_name}_$SRC" "$src_file_suffixes" $root_dir $frag_dir
559
560
561	# If we were asked to act recursively, then continue processing
562	# root_dir's contents.
563	if [ -n "$recursive_flag" ]; then
564
565		# Get a listing of the directories in $directory
566		sub_items=$(ls $root_dir)
567
568		# Descend into the contents of root_dir to generate the makefile
569		# fragments.
570		for item in $sub_items; do
571
572			# If item is a directory, and it's not in the ignore list, descend into it.
573			if [ -d "$root_dir/$item" ] && ! should_ignore $item ; then
574
575				gen_mkfiles $root_dir/$item $frag_dir/$item
576			fi
577		done
578	fi
579
580
581	# Exit peacefully
582	return 0
583}
584
585should_ignore()
586{
587	local item name
588
589
590	# Extract argument, the item that we may have to ignore.
591	item="$1"
592
593
594	# Process each index in ignore array
595	for i in "${ignore_i[@]}"; do
596
597		# Get the ith name
598		name=${ignore_name[$i]}
599
600		# If the current value of $name matches $item, then we need to
601		# signal to calling function that we SHOULD ignore $item.
602		# Notice that returning zero value means "success".
603		if [ "$item" = "$name" ]; then
604			return 0
605		fi
606	done
607
608
609	# If we got this far, then item is not in the ignore list, so we
610	# signal that we should NOT ignore item. Notice that returning
611	# a non-zero value means "failure".
612	return 1
613}
614
615is_in_list()
616{
617	local cur_item the_item item_list
618
619	# Extract argument.
620	the_item="$1"
621	item_list="$2"
622
623	# Check each item in the list against the item of interest.
624	for cur_item in ${item_list}; do
625
626		# If the current item in the list matches the one of interest
627		if [ "${cur_item}" = "${the_item}" ]; then
628
629			# Return success (ie: item was found).
630			return 0
631		fi
632	done
633
634	# If we made it this far, return failure (ie: item not found).
635	return 1
636}
637
638# The script's main entry point, passing all parameters given.
639main "$@"
640