makefile

Makefile:


make utility in unix is an interpretor for Makefile. Makefile is like a shell script (similar to test.csh, run.bash, etc). The only difference is that Makefile is not an executable (not sure why it's not required to be executable, as it may have unix cmds in it, and can be run by anyone). We write the script in a file called Makefile (note capital M in Makefile). Then we run the interpretor called "make", which interprets this Makefile (Makefile is the default file make looks for, we can also specify other files for make to look at) and produces desired outcome. make uses rules in Makefile to run (Makefile is placed in same dir as where make is run). It is very important utility in Linux, as many programs/applications use Makefile to generate executable files. If you want to write and compile your own large program, Makefile is essential there too. Makefile is basically a file which says what actions to take, depending on what dependencies it has. Makefile was written because some programmer forget to recompile a file that had changed. that caused him many hours of wasted time. make was written so that it could keep track of what changed, and recompile the needed files automatically, w/o user bothered with it. We can entirely do away with Makefile if we can manage everything manually.

Makefile is used extensively in generating executables for programs as C. It's also used as wrapper for calling multiple bash/csh scripts via just 1 cmd.

Very good and short intro to Makefile is here:

http://www.jfranken.de/homepages/johannes/vortraege/make_inhalt.en.html

Authentic make documentation from GNU: http://www.gnu.org/software/make/manual/make.html

Makefile Syntax:

Makefile has separate lines. Each line ends with a newline (no ; needed unless we put multiple cmds on same line). If we want to continue current line to next line, we can use "\" at end of line (before entering newline). That way "make" sees next line as continuation of previous line. General syntax of Makefile is ( [ ] implies it's optional):

target [ more targets] :[:] [ prerequisites ] [; commands] => If we want to put cmds along with prerequisites, then we need to have ; to separate cmds from prerequisites.

[ <tab> commands ] => Note that there needs to be a tab (multiple spaces) before commands in every line.

[ <tab> commands ]

...

Makefile consists of 4 things:
1. comments: start with #
2. defn of variables/functions: myvar = adb, or myvar := adb (spaces don't matter. i.e myvar=adb is fine too). Now myvar can be accessed anywhere in Makefile by using $(myvar). We should always use $(myvar) instead of $myvar, as myvar may not be treated as single var when not nside (), causing $myvar to be expanded as $m followed by yvar. We can apply var to subset of string. i.e: $(myvar)_module/ABC/X will expnad to adb_module/ABC/X. Curly braces {} can be used too. ". =" expands var/func at use time, while ":=" expands them at declaration time.

myvar ?= adb => ?= is a conditional assignment and assigns a value to var only if that var hasn't been defined previously.

    ex:  XTERM_TITLE = "$@: `pwd`"

    ex: ARGS = -block $(DES) $(SCR)/tmp/a.tx -name kail


3. Includes: to include other Makefile, since 1 Makefile may get too big. When "-" is placed in front of any cmd, it ignores that cmd in case of errors and moves on. If - not placed before a cmd, and that cmd fails, then make aborts. -include Makefile.local will execute Makefile.local, and if Makefile.local is mssing, then it will keep on mocing (w/o aborting)

4. Rules: hello: a; @echo hello

5. other cmds: all unix cmds that can be used in shell, can be used in Makefile. (ex: echo, for ... do .. done, export, etc). These cmds can be put directly on action/cmd line of rules.

ex: @rm -rf $(DES) => removes files. @-rm -rf * => - in front causes the cmd to be ignored if there's any error executing the cmd, and make moves forward with next line.

Rules: We will talk about rules, since they are the heart of Makefile:
ex: below ex defines rules for target hello & diskfree.  Rules have multiple lines. 1st line is rule or dependency (or prerequisite) line, 2nd line is action line. 1st line says that the prerequisite has to be satisfied before 2nd line can be run. It will check to see if the prerequisite is upto date based on it's own dependencies, if so it will run action line, else it will run prerequisite to make it upto date based on it's dependencies. (1st variable on the rule line (ie "hello" in ex below) is the name of the target that can be specified on unix cmd line as "make hello")
hello: ; => dependency line (or pre-requisite line): It's blank here as we don't have any dependency. We can put a ; at end of line if we want to put next cmd on this line itself, otherwise it's not needed.
       @echo Hello => action line: running "make hello" outputs "Hello" on screen. @ prevents make from announcing the command it's going to do. So, @ prevents the cmd "echo Hello" from getting printed on screen. Since echo is already printing "Hello" on screen, we do not want 2 lines to be printed on screen (i.e "echo Hello" followed by "Hello"). That's why we put a @
diskfree: ;
          df -h => running "make diskfree" runs "df -h" which outputs diskfile usage on screen. Since @ is not used, it outputs the cmd "df -h" on screen

#create a Makefile and copy the above 2 lines in it. Then run 2 cmds below on cmd line in shell. If we don't tell make a target, it will simply do the first rule:
make => will do target hello, resulting in "Hello" on screen.
make diskfree hello => it will do these targets in order, first diskfree, then hello.

Make options:

There are various options that can be used when running make. They can be found on gnu link above. Some of the imp ones are below:

#above we did not specify which makefile to use.  It uses Makefile in current dir. To be specific, we say:
make -f makefile.cvc => runs make on this makefile.cvc. no target specified, so does the first rule, which is "cvc" which makes binary for cvc. To run clean, do:
make -f makefile.cvc clean => runs rule "clean" for this Makefile.

make -C dir1/dr2 all => -C specifies the dir where Makefile is. all is a convention. "all" rule is defined which runs sub targets to build the entire project. Since usually we want to run "all", we put it as first target, so that just running "make" will run "make all".

make hello -n => -n option shows what all steps will be run for target "hello" without actually running the steps. This helps us in understanding the sequence of steps that will be run, when analyzing a Makeful. This is very useful in debug and used a lot. Always run any target with "-n" option to check what it's going to do, and then run it without "-n" to run it.

pattern matching : % can be used match multiple targets. ‘%’ which acts as a wildcard, matches any number of any characters within a word. Ex:

final_%_run: %.out ;

           @echo "Hell" => Now, when we run "make final_2ab_run", then this rule gets run, as target matches name in Makefile with % matching "2ab". It has a dep 2ab.out (since wildcard % is assigned 2ab). We cannot use % in action line, as it's not substituted with "2ab". If we run "make final_abc_run", then again the same target gets run, but now % is replaced by abc. So, dep is now abc.out. NOTE: when we use % notation, then "make" w/o any target will error out, as there's no default matching target.

Phony targets:

.PHONY: By default, Makefile targets are "file targets" - they are used to build files from other files. Make assumes its target is a file. i.e "hello: abc ;echo .." implies hello and abc are files (hello and abc files are generated via cmds in Makefile). It looks at timestamp of hello and abc files to decide what to do. However, many tagets such as "clean", "all", "install" are not files, so if there is a file with same name, make will start looking at timestamp of such files to determine what to do.  The .PHONY directive tells make which target names have nothing to do with potentially existing files of the same name. PHONY implies these targets are not real.  .PHONY target implies target that is always out of date and always runs ignoring any time stamps on file names. ex: .PHONY setup => this will cause setup to be run irrespective of the state of file "setup" if any.

automatic variables: On top of var defined by user, we also have in built var:

  •  $@ in action line suubstitutes it with target name
    ex: hello: ;
               @echo printmsg $@ => prints "printmsg hello" on screen.
     
  • $< in action line substitutes it with name of 1st pre-requisite in dep line. To get names of all prereq with spaces in b/w them, use $^ or $+ ($^ removes duplicate prereq, while $+ retains all of them).
    ex: tiger.pdf: tiger.ps; ps2pdf $< => make tiger.pdf will run this cmd: ps2pdf tiger.ps

 



------
1. Example of Makefile and make:

ex: executable "sum" is to be generated from 2 C files (main.c,sum.c) and 1 h file sum.h, which is included in both c files.
run make => reads Makefile, creates dependency tree and takes necessary action.


#binary exe
sum: main.o sum.o => dependency line states that exe sum depends on main.o and sum.o
      cc -o sum main.o sum.o => cmd/action line states how pgm should be compiled if 1 or more .o files have changed.

#main dep
main.o: main.c sum.h => dep line for main.o. we can omit main.c, since built in rule for make says that .o file depends on corresponding .c file.
        cc -c main.c => cmd line stating how to generate main.o

#sum dep
sum.o: sum.c sum.h => similarly we can omit sum.c from here
       cc -c sum.c

#above 2 dep, main & sum can be combined into 1:
main.o sum.o: sum.h     => means both .o files depend on sum.h (dep on main.c and sum.c is implied automatically)
              cc -c $*.c => macro $*.c expands to main.c for main.o and sum.c for sum.o



2. Example of Makefile and make:

ex: executable for arm processor:

Makefile: /data/tmp/Makefile
run:  make TGT=hellow

#define variables doe compiler, assembler, linker and elf
tool-rev        =       -4.0-821
CC              =       armcc $(tool-rev)

#compiler options
CCFLAGS         =       $(CPUTARGET) -I $(INCPATH) -c --data_reorder \
                        --diag_suppress=2874 \
                        --asm

#dependency rule to state
IKDEPS_MAIN     =       CMSIS/Core/CM0/core_cm0.h CMSIS/Core/CM0/core_cm0.c cm0ikmcu.h IKtests.h IKtests.c IKConfig.h debug_i2c.h sporsho_tb.h Makefile
IKDEPS          =       $(IKDEPS_MAIN) debugdriver
IKOBJS          =       boot.o system_cm0ikmcu.o retarget_cm0ikmcu.o IKtests.o debug_i2c.o sporsho_tb.o

# Performance options (adds more options to compiler flag)
CCFLAGS         +=      -O2 -Otime -Ono_autoinline -Ono_inline

#Rules to create dependency tree
#top level target depends on TGT.bin
$(TGT):         $(TGT).bin => TGT depends on TGT.bin
                @echo => cmd line states that dont echo the cmd.
 
#expands to fromelf -4.0-821 --bin -o kail_rtsc.bin kail_rtsc.elf
$(TGT).bin:     $(TGT).elf
                $(FROMELF) --bin -o $@ $<

#expands to armlink -4.0-821 --map --ro-base=0x0 --rw-base=0x20000020 --symbols --first='boot.o(vectors)' --datacompressor=off --info=inline -o kail_rtsc.elf kail_rtsc.o boot.o system_cm0ikmcu.o retarget_cm0ikmcu.o IKtests.o debug_i2c.o sporsho_tb.o
$(TGT).elf:     $(TGT).o $(IKOBJS)
                $(LD) $(LDFLAGS) -o $@ $(TGT).o $(IKOBJS)

#expands to armcc -4.0-821 --cpu=Cortex-M0 -I ./CMSIS/Core/CM0 -c --data_reorder --diag_suppress=2874 --asm  -O2 -Otime -Ono_autoinline -Ono_inline -o kail_rtsc.o kail_rtsc.c
$(TGT).o:       $(TGT).c $(IKDEPS)
                $(CC) $(CCFLAGS) -o $@ $< => $@ = TGT.o, $< = TGT.c

#all specifies what to run when no target is specified, i.e when we run just "make"
all:    debug

#similarly we specify rules for debug_i2c.o, sporsho_tb.o, boot.o, boot_evm.o, sporsho1_lib.o, retarget_cm0ikmcu.o, system_cm0ikmcu.o, system_cm0ikmcu_evm.o, IKtests.o.
#ex for IKtests. expands to armcc -4.0-821 --cpu=Cortex-M0 -I ./CMSIS/Core/CM0 -c --data_reorder --diag_suppress=2874 --asm -O2 -Otime -Ono_autoinline -Ono_inline -o IKtests.o IKtests.c
IKtests.o:      IKtests.c $(IKDEPS_MAIN)
                $(CC) $(CCFLAGS) -o $@ $<

#clean specifies that rm everything when running "make clean"
clean:
                rm -f *.bin *.elf *.o *.s *~
-----------------------------------

Advanced section:

1. Function for string substitution:

A. subst => simple substitution => $(subst from,to,text) => Performs a textual replacement on the text text: each occurrence of from is replaced by to. The result is substituted for the function call.

ex: Var1 = $(subst ee,EE,feet on the street) => new string "fEEt on the strEEt" assigned to Var1

B. patsubst => pattern substitution => $(patsubst pattern,replacement,text) => Finds whitespace-separated words in text that match pattern and replaces them with replacement. Here pattern may contain a ‘%’ which acts as a wildcard, matching any number of any characters within a word. If replacement also contains a ‘%’, the ‘%’ is replaced by the text that matched the ‘%’ in pattern. 

ex: Var2 = $(patsubst %.c,%.o,x.c.c bar.c) => .c replaced with .o, everything else is copied exactly as it's % on both pattern and replacement. so, final value assigned to Var2 is "x.c.o bar.o"

2. substitution reference: A substitution reference substitutes the value of a variable with alterations that you specify. It's identical to patsubst function above, so there's really no need for this, but it's provided for compatibility with some implementations of make.

Form => $(var:a=b)’ or ‘${var:a=b} => () or {} mean same thing. Takes the value of the variable var, replace every "a" at the end of a word with "b" in that value, and substitute the resulting string. Only those "a" that are at end of word (i.e followed by whitespace) are replaced. All other "a" remian unaltered. This form is same as patsubst => $(patsubst a,b, $(var))

foo := a.o b.o c.o
bar := $(foo:.o=.c) => It says to look at all words of var "foo", replace every word wherever "o" is the last character of that word with "c". Then assign this modified string to var "bar". So, bar gets set to "a.c b.c c.c". Here wild card matching of patsubst not used.
bar := $(foo:%.o=%.c) => Here wildcard char % is used for matching. So, bar gets set to "a.c b.c c.c". Here wild card matching of patsubst is used.

ex: following is in a makefile to create different target based on what TOP_MOD is being set to. TOP_MOD is assigned value from cmd line or from some other file: TOP_MOD := abc

target1_me: ${TOP_MOD:%=%.target1_me} ; => whenever we run target1.me from make cmdline, it calls this target. It has dependency specified within ${..}. Since this is substitution reference, the whole ${ .. } get assigned abc.target1_me. So, make looks for target abc.target1_me.
%.target1_me: ; echo "Hell"; => make finds this target as % expands to abc, so it starts running this target with whatever action it's supposed to do. In effect, we redirected flow to" abc.target1_me" target. This is helpful in cases where same target needs to be run with multiple times, but with different options. 

ex: