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