There's a somewhat similar paradox that seems to apply to most developers. No matter how much we love what we do, it seems like one of the most enjoyable elements of our work is finding tools that help us do less of it. With their large following, the C++, C#.NET and Java languages are hotbeds of development for these time-saving tools -- for example, some of the tools commonly used for short cuts and jump-starts in TopCoder component development include:
In this article, I'll review Make, "a tool which controls the generation of executables and other non-source files of a program from the program's source files" (according to GNU Make home). Created in 1977, 'make' is a Unix utility which helps developers manage how their applications are built from source code, by giving users the abilty to set up source dependancies, add callable shell scripts, and perform many levels of string manipulation. make works by building 'target' files, first recursively making any of its 'dependancy' targets, while also skipping targets whose dependancies have not changed since the last time they were made -- a useful optimization for large projects/targets. The build information is obtained from what is called a 'Makefile', either specified when make is run, or using the environment's default file.
One version of make, 'GNU make', can be found contained within most GNU/Linux distributions. As such, it has become a popular build tool among developers of both small and large projects -- if you take a look at many open-source project file-listings, chances are that the ubiquitous 'Makefile' will make an appearance.
Distributions of make usually come with their own default Makefile - for instance, if you have a file 'me.cpp', and type 'make me', it will probably attempt to build your target by automatically running 'g++ me.cpp -o me'. However, you can control what make does by providing it with your own Makefile. For instance, assume you have the following file in your directory, calling it 'Makefile' (with no extension):
me.o : me.cpp @echo "Making object..." g++ -Wall -g -c me.cpp me : me.o @echo "Making executable...." g++ -Wall -g me.o -o meCalling 'make me' should result in an output similar to the following:
Making object... g++ -Wall -g -c me.cpp Making executable.... g++ -Wall -g me.o -o meAs you can see, make has built the 'me' target, by first building the 'me.o' target that it depends on. To build this, it has echoed a message to the screen, then run the supplied compilation command. Once this was built, the initial target could then be built by using its given commands.
Now......try it again - you should get a message telling you that 'me' is up to date. This is because it is newer than all of its dependencies, so make realizes it does not need to be built again. However, if you were to update the timestamp on me.o -- 'touch me.o', say -- then try to build it again, you would notice that only the executable is built, as the object is still more recent than its dependency (me.cpp). This optimized building is a major benefit of Makefiles, and a reason why they are so popular.
All Makefiles have a command setup, as follows:
# comments start with the hash character # string assignments are simple (name)=(value), for example: wish=$(mood) Birthday mood=Happy name=coder print=@echo # each target description is: # target(s) : [optional dependency list] # command, starting with tab character ('\t') # [optional other lines, all starting with tabs] middle : FORCE $(print) -e "$(wish) to you!\n\ $(wish) dear $(name)," end finish : FORCE $(print) "$(wish) to you!" beginning start : FORCE $(print) "$(wish) to you!" song : start middle end FORCE :Now, running 'make song' should print out:
Happy Birthday to you! Happy Birthday to you! Happy Birthday dear coder, Happy Birthday to you!The best way to think about how makefiles work is string replacements -- for example, the command for 'middle' is first joined into one line, then $(print) is replaced by @echo, $(wish) by $(mood) Birthday by Happy Birthday, etc... Finally, the whole command @echo -e "Happy Birthday to you!\nHappy Birthday dear coder," is formed, then executed, in a shell.
Some things that may be useful to know about structure when writing your own makefiles include:
As Makefiles contain commands executed in a shell; it is possible to add shell scripts into the command lists, and have these executed when make-ing.
However, these may depend on the user running a given shell type -- instead, GNU Make provides its own Makefile functions, which can help add programming to a makefile. You can even define your own functions for use in a makefile. Consider the following file:
loop_demo : bash_loop make_loop bash_loop : @echo "Bash loop.." @for ((bA=0; bA<10; bA++)); \ do echo -n "-$- "; \ done; echo; make_loop : @echo "Make loop.." @echo $(foreach mA, $(sort "1" "4" "2" "8" "5" "7" "9" "3" "6" "0"), "-$(mA)-")Executing make with this at the top of a makefile will give:
Bash loop.. -0- -1- -2- -3- -4- -5- -6- -7- -8- -9- Make loop.. -0- -1- -2- -3- -4- -5- -6- -7- -8- -9-Have a read through the files listed at the link above -- the important thing to keep in your head when using them is that they are all string-based, and required to be within one set of brackets. It may require a bit of getting used to, but the functions are powerful enough for most purposes -- for example, here is a snippet that compiles all .cpp files in a set of directories to .o files in another location. While not necessarily the shortest way to do it, this demonstrates the use of a few functions (nested loops, wildcard expansion and pattern replacement):
target=$(patsubst $(source_direc)/%.cpp, $(obj_direc)/%.o,$(source)) ... $(foreach source, \ $(foreach dir, $(direc_list),$(wildcard $(source_direc)/$(dir)/*.cpp)), \ echo "compiling" $(source) "to" $(target) "..."; \ g++ $(source) -c -o $(target) $(LFLAGS); \ )
Another useful built-in ability of GNU Make allows you to use wildcards in the targets and dependancies, and then reference the files using tokens called 'automatic variables', including:
% : %.cpp g++ -o $* $<Then running 'make me' will search for me.cpp, replace the $* with the matching string ('me' in this case) and $< with the dependancy (me.cpp)
Due to its frequent use in many distributed projects, there are a few popular make targets that can often be found within Makefiles. These include: