001 #!/bin/bash
002 # Copyright (c) 2009, Josef Nygrin, http://www.paskvil.com/
003 # All rights reserved.
004 #
005 # This is a script to generate Makefile based on source files (.c, .cpp, .c++
006 # and .cxx) present in current directory, using 'gcc -MM'.
007 #
008 # Redistribution and use in source and binary forms, with or without
009 # modification, are permitted provided that the following conditions are met:
010 # * Redistributions of source code must retain the above copyright
011 # notice, this list of conditions and the following disclaimer.
012 # * Redistributions in binary form must reproduce the above copyright
013 # notice, this list of conditions and the following disclaimer in the
014 # documentation and/or other materials provided with the distribution.
015 #
016 # THIS SOFTWARE IS PROVIDED BY JOSEF NYGRIN ''AS IS'' AND ANY
017 # EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
018 # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
019 # DISCLAIMED. IN NO EVENT SHALL JOSEF NYGRIN BE LIABLE FOR ANY
020 # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
021 # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
022 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
023 # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
024 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
025 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
026 #
027 # Requirements
028 # - the script: gcc (and bash, ls, mkdir)
029 # - the 'make profile' target: gprof, gprof2dot.py, dot (graphwiz)
030 # - the 'make debug' target: gdb
031 # - the automatic build number increment: awk (and mv)
032 #
033 # Future extensions (todo list):
034 # - add the checks in -f, -l and -c options as indicated
035 # - add support for subdirectories
036 # - more checks done (valid compiler, executable name, ...)
037 # - support for other -MM compatible compilers
038 # - support for other command line syntaxes
039 # - allow different compilers (and settings) for .c, .cpp and .cxx individually
040
041 ################################################################################
042 function print_usage_to_stderr
043 {
044 echo "Usage:" > /dev/stderr;
045 echo " $0 [options] exec" > /dev/stderr;
046 echo "Run '$0 -h' or '$0 --help' for more info." > /dev/stderr;
047 }
048
049 ################################################################################
050 function print_help
051 {
052 printf "Not so Simple Makefile generator, gen_make.sh, version 2.0.\n";
053 printf "Copyright (c) 2009, Josef Nygrin, http://justcheckingonall.wordpress.com/\n";
054 printf "\n";
055 printf "Usage:\n";
056 printf " \033[1mgen_make.sh [options] exec\033[0m\n";
057 printf "\n";
058 printf "The resulting Makefile is printed to \033[1mstdout\033[0m.\n";
059 printf "gen_make currently supports building of executables, static libraries, and\n";
060 printf "shared libraries. This choice is done based on format of \033[1mexec\033[0m parameter.\n";
061 printf "\n";
062 printf " \033[1mexec\033[0m\n";
063 printf " name of the output binary file to build\n";
064 printf "\n";
065 printf "Options:\n";
066 printf " \033[1m-h\033[0m\n";
067 printf " \033[1m--help\033[0m\n";
068 printf " Prints this help message.\n";
069 printf " \033[1m-s\033[0m\n";
070 printf " Silent mode. On default, gen_make script prints out some info about\n";
071 printf " selected configuration to \033[1mstderr\033[0m, this switch prevents this.\n";
072 printf " \033[1m-t\033[0m target\n";
073 printf " Build target. gen_make supports targets \033[1md, dp, r, rp\033[0m:\n";
074 printf " \033[1m'd'ebug\n";
075 printf " 'd'ebug 'p'rofile\n";
076 printf " 'r'elease\033[0m (default)\n";
077 printf " \033[1m'r'elease 'p'rofile\033[0m\n";
078 printf " For 'profile' targets, 'make profile' command is included, that creates\n";
079 printf " profile.txt using gprof(1), and profile.png using gprof2dot.py and dot.\n";
080 printf " \033[1m-f\033[0m 'flags'\n";
081 printf " Additional flags to pass to compiler.\n";
082 printf " \033[1m-l\033[0m 'libs'\n";
083 printf " Link libraries. Use \033[1m-l 'mylib urlib'\033[0m for '-lmylib -lurlib'.\n";
084 printf " \033[1m-c\033[0m compiler\n";
085 printf " Select compiler. Default is \033[1mg++\033[0m.\n";
086 printf " \033[1m-bn\033[0m\n";
087 printf " Enable build number. If selected, gen_make will create BuildNum.h header\n";
088 printf " file, if it does not exist yet, and will include script to automatically\n";
089 printf " increment the build number on each build.\n";
090 printf " \033[1m-od\033[0m dir_name\n";
091 printf " Output directory. gen_make chooses name of output directory based on\n";
092 printf " selected build target, and creates the directory. This switch allows\n";
093 printf " you to select different directory.\n";
094 printf " \033[1m-exc\033[0m 'files'\n";
095 printf " List of files excluded from build. E.g. \033[1m-exc 'file1.cpp file2.cpp'\033[0m.\n";
096 printf " \033[1m-par\033[0m 'parameters'\n";
097 printf " Run parameters for the executable. gen_make includes 'make run' and\n";
098 printf " 'make debug' for executables, for executing and debugging using gdb(1),\n";
099 printf " respectively. Here you can provide any parameters to the executable.\n";
100 printf " \033[1m-post\033[0m 'command'\n";
101 printf " Provide any command that should be executed as a post-build step.\n";
102 printf " E.g. \033[1m-post 'echo We are done...'\033[0m (single quotes).\n";
103 printf " Command itself is not echo-ed by the make.\n";
104 }
105
106 ################################################################################
107 # if no parameter passed, print out usage and quit
108 if [ $# -le '0' ]
109 then
110 print_usage_to_stderr;
111 exit;
112 fi
113
114 # name of executable
115 exec="";
116 # target - 'd'ebug, 'r'elease, 'd'ebug 'p'rofile, 'r'elease 'p'rofile
117 # for profile targets, "make profile" will be also created, using the gprof2dot.py
118 target="";
119 # generate 'make profile' target?
120 profile_target="no";
121 # common flags to use based on target
122 cflags="";
123 # additional flags provided by user
124 addflags="";
125 # list of additional link libraries, mylib for -lmylib
126 link_libs="";
127 # compiler to use
128 compiler="g++";
129 # generate BuildNum.h and include script to increase build num?
130 build_num="no";
131 # output directory
132 out_dir="";
133 # list of files to exclude from builds
134 exclude_files="";
135 # parameters to pass to executable on "make run" and "make debug"
136 run_params="";
137 # be silent? i.e. do not print conf info to stderr?
138 silent="no";
139 # post-build step command(s)
140 postbuild_step="";
141
142 ################################################################################
143 # parse input params
144 until [ -z "$1" ]
145 do
146 case "$1" in
147 ############################################################################
148 "-h") # print help
149 print_help;
150 exit;
151 ;;
152 "--help") # print help
153 print_help;
154 exit;
155 ;;
156 ############################################################################
157 "-s") # silent?
158 silent="yes";
159 ;;
160 ############################################################################
161 "-t") # target
162 shift; # now we need the param after '-t'
163 target="$1";
164 case $target in
165 "d")
166 cflags="-g -Wall -Werror -DDEBUG -D_DEBUG";
167 ;;
168 "dp")
169 cflags="-p -g -Wall -Werror -DDEBUG -D_DEBUG";
170 profile_target="yes";
171 ;;
172 "r")
173 cflags="-O3";
174 ;;
175 "rp")
176 cflags="-p -O3";
177 profile_target="yes";
178 ;;
179 *)
180 echo "Unsupported build target provided with -t, '$target'!" > /dev/stderr;
181 echo "Supported targets: d, r, dp, rp." > /dev/stderr;
182 exit;
183 esac
184 ;;
185 ############################################################################
186 "-f") # additional flags
187 shift;
188 # TODO: how can we check if flags are correct ones?
189 addflags="$1";
190 ;;
191 ############################################################################
192 "-l") # link libraries
193 shift;
194 # save the list; TODO: how can we check correctness?
195 link_libs="$1";
196 ;;
197 ############################################################################
198 "-c") # compiler
199 shift;
200 # TODO: how can we check this really is a compiler? run it?
201 compiler="$1";
202 ;;
203 ############################################################################
204 "-bn") # build number enabled
205 build_num="yes";
206 ;;
207 ############################################################################
208 "-od") # output directory
209 shift;
210 out_dir="$1";
211 ;;
212 ############################################################################
213 "-exc") # list of exclude files
214 shift;
215 exclude_files="$1";
216 ;;
217 ############################################################################
218 "-par") # run parameters to the executable
219 shift;
220 run_params="$1";
221 ;;
222 ############################################################################
223 "-post") # post-build step command(s)
224 shift;
225 postbuild_step="$1";
226 ;;
227 ############################################################################
228 *) # mistake, or executable name
229 exec="$1";
230
231 # if first letter is '-', this is most probably invalid option
232 if [ ${exec:0:1} = "-" ]
233 then
234 echo "Unknown option '$exec' passed!" > /dev/stderr;
235 print_usage_to_stderr;
236 exit;
237 fi
238
239 # this should be last param to script (i.e. '-z $2')
240 if [ ! -z $2 ]
241 then
242 echo "The provided executable name '$exec' should be last parameter!" > /dev/stderr;
243 print_usage_to_stderr;
244 exit;
245 fi
246 ;;
247 esac
248 shift;
249 done
250
251 ################################################################################
252 # the only required information is name of executable
253 if [ -z $exec ]
254 then
255 echo "No name of executable provided!" > /dev/stderr;
256 print_usage_to_stderr;
257 exit;
258 fi
259
260 ################################################################################
261 # determine executable's type
262 exec_type="exe";
263 if [[ "$exec" == lib*.a || "$exec" == *.lib ]]
264 then exec_type="lib";
265 elif [[ "$exec" == lib*.so || "$exec" == *.dll ]]
266 then exec_type="dll";
267 fi
268
269 ################################################################################
270 # if target is not set, presume 'r'elease
271 if [ -z $target ]
272 then
273 target="r";
274 cflags="-O3";
275 fi
276
277 ################################################################################
278 # if no output directory set, choose based on target
279 if [ -z $out_dir ]
280 then
281 case $target in
282 "d")
283 out_dir="debug_build";
284 ;;
285 "dp")
286 out_dir="debug_profile";
287 ;;
288 "r")
289 out_dir="release_build";
290 ;;
291 "rp")
292 out_dir="release_profile";
293 ;;
294 esac
295 fi
296
297 # create the binary output directory, if it doesn't exist yet
298 mkdir $out_dir &> /dev/null;
299
300 ################################################################################
301 # print info to stderr for user to review, if not silent
302 if [ $silent = "no" ]
303 then
304 # print the collected information to stderr
305 echo "Name of executable: $exec." > /dev/stderr;
306 echo "Type of executable: $exec_type." > /dev/stderr;
307 echo "Selected target: $target." > /dev/stderr;
308 echo "Compiler flags: $cflags." > /dev/stderr;
309 if [ ! -z "$addflags" ]; then echo "Additional flags: $addflags." > /dev/stderr;
310 else echo "Additional flags: (none)." > /dev/stderr; fi
311 if [ ! -z "$link_libs" ]; then echo "Link libraries: $link_libs." > /dev/stderr;
312 else echo "Link libraries: (none)." > /dev/stderr; fi
313 echo "Selected compiler: $compiler." > /dev/stderr;
314 echo "Build number header and auto-increment: $build_num." > /dev/stderr;
315 echo "Output directory: $out_dir." > /dev/stderr;
316 if [ ! -z "$exclude_files" ]; then echo "Files excluded from compilation: $exclude_files." > /dev/stderr;
317 else echo "Files excluded from compilation: (none)." > /dev/stderr; fi
318 if [ ! -z "$run_params" ]; then echo "Run parameters: $run_params." > /dev/stderr;
319 else echo "Run parameters: (none)." > /dev/stderr; fi
320 echo "Include 'make profile': $profile_target." > /dev/stderr;
321 if [ ! -z "$postbuild_step" ]; then echo "Post-build step: $postbuild_step." > /dev/stderr;
322 else echo "Post-build step: (none)." > /dev/stderr; fi
323 fi
324
325 ################################################################################
326 # print out the common header
327 printf "#\n";
328 printf "# Makefile for '$exec'.\n";
329 printf "#\n";
330 printf "# Type 'make' or 'make $exec' to create the binary.\n";
331 printf "# Type 'make clean' or 'make clear' to delete all temporaries.\n";
332 if [ $exec_type = "exe" ]
333 then
334 printf "# Type 'make run' to execute the binary.\n";
335 printf "# Type 'make debug' to debug the binary using gdb(1).\n";
336 fi
337 if [[ $exec_type = "exe" && $profile_target = "yes" ]]
338 then
339 printf "# Type 'make profile' to create profile image using gprof(1) and gprof2dot.py\n";
340 printf "# after a successful run of the executable; will create profile.txt and .png.\n";
341 fi
342 printf "#\n";
343 printf "\n";
344 printf "# build target specs\n";
345 printf "CC = $compiler\n";
346 printf "CFLAGS = $cflags $addflags\n";
347 printf "OUT_DIR = $out_dir\n";
348 printf "LIBS =";
349 for lib in $link_libs
350 do
351 printf " -l$lib";
352 done
353 printf "\n\n";
354 printf "# first target entry is the target invoked when typing 'make'\n";
355 printf "default: $exec\n";
356 printf "\n";
357
358 ################################################################################
359 # print for each file with provided extension ($1 - first
360 # parameter) string " $(OUT_DIR)/filename.o"
361 function bin_deps_for_ext
362 {
363 ls *.$1 &> /dev/null;
364 # did the 'ls' exit with 0? (all ok)
365 if [ $? -eq '0' ]
366 then
367 for i in `ls *.$1`
368 do
369 # is this an excluded file? if so, do not list it
370 excl=0;
371 for file in $exclude_files; do
372 if [ $file = $i ]; then excl=1; break; fi
373 done
374 if [ $excl -eq 0 ]; then printf " \$(OUT_DIR)/$i.o"; fi
375 done
376 fi
377 }
378
379 # print the binary's deps (depends on all object files,
380 # one for each source .c, .cpp and .cxx file)
381 # if the exec's name ends with '.a' or '.lib', treat it as static library
382 printf "$exec:";
383
384 # output binary depends on all created object files
385 bin_deps_for_ext c;
386 bin_deps_for_ext cpp;
387 bin_deps_for_ext c++;
388 bin_deps_for_ext cxx;
389 printf "\n";
390
391 # print the build number increment script, and create BuildNum.h, if necessary
392 if [ $build_num = "yes" ]
393 then
394 # if the BuildNum.h does not exist yet, create it
395 ls BuildNum.h &> /dev/null;
396 if [ $? -ne '0' ]
397 then
398 printf "// Version and build number header for $exec.\n" > BuildNum.h;
399 printf "// Generated by gen_make.sh, build number is auto-incremented on build.\n" >> BuildNum.h;
400 printf "//\n\n" >> BuildNum.h;
401 printf "#ifndef __BuildNum_h__\n" >> BuildNum.h;
402 printf "#define __BuildNum_h__\n\n" >> BuildNum.h;
403 printf "#define Version1\t1\n" >> BuildNum.h;
404 printf "#define Version2\t0\n" >> BuildNum.h;
405 printf "#define Version3\t0\n" >> BuildNum.h;
406 printf "#define BuildNum\t1\n\n" >> BuildNum.h;
407 printf "#endif//__BuildNum_h__\n\n" >> BuildNum.h;
408 fi
409 # add the script to increase build number on each build
410 printf "\t@echo -n 'Increasing build number in BuildNum.h... '\n";
411 printf "\t@awk '!/define BuildNum/ {print} /define BuildNum/ {print \"#define BuildNum\\\\t\"\$\$3+1}' BuildNum.h > BuildNum.h~\n";
412 printf "\t@mv BuildNum.h~ BuildNum.h\n";
413 printf "\t@echo Done.\n";
414 fi
415
416 # print command used to put the output binary together, depending on type
417 printf "\t@echo -n 'Linking $exec... '\n";
418 if [ "$exec_type" == "lib" ]; then printf "\t@ar rsc $exec";
419 elif [ "$exec_type" == "dll" ]; then printf "\t@\$(CC) -shared -Wl,-soname,$exec -o $exec";
420 else printf "\t@\$(CC) \$(CFLAGS) -o $exec"; fi
421
422 # and print its input - all created object files
423 bin_deps_for_ext c;
424 bin_deps_for_ext cpp;
425 bin_deps_for_ext c++;
426 bin_deps_for_ext cxx;
427 printf " \$(LIBS)\n";
428 printf "\t@echo Done.\n";
429
430 if [ ! -z "$postbuild_step" ]; then printf "\t@echo Executing post-build step...\n"; printf "\t@$postbuild_step\n"; fi
431 printf "\n";
432
433 ################################################################################
434 # print out dependencies and build rules for all files
435 # with extension passed as parameter ($1)
436 function print_deps_for_ext
437 {
438 ls *.$1 &> /dev/null;
439 # did the 'ls' exit with 0? (all ok)
440 if [ $? -eq '0' ]
441 then
442 for i in `ls *.$1`
443 do
444 # is this an excluded file? if so, do not list it
445 excl=0;
446 for file in $exclude_files; do
447 if [ $file = $i ]; then excl=1; break; fi
448 done
449 if [ $excl -eq 0 ];
450 then
451 gcc -MM -MG -MT \$\(OUT_DIR\)/$i.o $i;
452 printf "\t@echo -n 'Compiling $i... '\n";
453 printf "\t@\$(CC) \$(CFLAGS) -o \$(OUT_DIR)/$i.o -c $i\n";
454 printf "\t@echo Done.\n\n";
455 fi
456 done
457 fi
458 }
459
460 # print out individual deps for object files
461 print_deps_for_ext c;
462 print_deps_for_ext cpp;
463 print_deps_for_ext c++;
464 print_deps_for_ext cxx;
465
466 ################################################################################
467 # add the 'run' and 'debug' target
468 if [ $exec_type = "exe" ]
469 then
470 printf "run:\n";
471 printf "\t./$exec $run_params\n\n";
472 printf "debug:\n";
473 if [ -z "$run_params" ]; then
474 printf "\tgdb ./$exec\n\n";
475 else
476 printf "\tgdb --args ./$exec $run_params\n\n";
477 fi
478 fi
479
480 ################################################################################
481 # add the 'create profile' target
482 #
483 # this target generates profile.txt using gprof(1),
484 # and profile.png using gprof2dot.py script and dot tool
485 if [[ $exec_type = "exe" && $profile_target = "yes" ]]
486 then
487 printf "profile:\n"
488 printf "\t@echo -n 'Creating profile.txt and profile.png... '\n";
489 printf "\t@gprof $exec > profile.txt && ./gprof2dot.py profile.txt | dot -Tpng -o profile.png\n";
490 printf "\t@echo Done.\n\n";
491 fi
492
493 ################################################################################
494 # add the 'clean up' target - 'make clean', 'make clear'
495 printf "clean:\n"
496 printf "\t@echo -n 'Removing all temporary binaries... '\n";
497 printf "\t@rm -f $exec \$(OUT_DIR)/*.o\n";
498 printf "\t@echo Done.\n\n";
499 printf "clear:\n"
500 printf "\t@echo -n 'Removing all temporary binaries... '\n";
501 printf "\t@rm -f $exec \$(OUT_DIR)/*.o\n";
502 printf "\t@echo Done.\n\n";
503
© 2011 Josef Nygrin - paskvil.com