Warning: Declaration of action_plugin_subjectindex_indexer::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php on line 15

Warning: Declaration of action_plugin_mathjax_enable::register(Doku_Event_Handler &$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/mathjax/action/enable.php on line 62

Warning: Declaration of action_plugin_googleanalytics::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/googleanalytics/action.php on line 40

Warning: Declaration of action_plugin_folded::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/folded/action.php on line 40

Warning: Declaration of action_plugin_hidden::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/hidden/action.php on line 28

Warning: Declaration of action_plugin_include::register(&$controller) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/include/action.php on line 354

Warning: Declaration of action_plugin_tag::register(&$contr) should be compatible with DokuWiki_Action_Plugin::register(Doku_Event_Handler $controller) in /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/tag/action.php on line 175

Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/auth.php on line 532

Warning: preg_replace(): The /e modifier is no longer supported, use preg_replace_callback instead in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/auth.php on line 818

Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/actions.php on line 656

Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/actions.php on line 656

Warning: Cannot modify header information - headers already sent by (output started at /data/web/virtuals/28604/virtual/www/subdom/bo/lib/plugins/subjectindex/action/indexer.php:15) in /data/web/virtuals/28604/virtual/www/subdom/bo/inc/actions.php on line 656
======Exercise 28: Intermediate Makefiles====== In the next three Exercises you'll create a skeleton project directory to use in building your C programs later. This skeleton directory will be used in the rest of the book, and in this exercise I'll cover just the Makefile so you can understand it. The purpose of this structure is to make it easy to build medium sized programs without having to resort to configure tools. If done right you can get very far with just GNU make and some small shell scripts. ======The Basic Project Structure====== The first thing to do is make a c-skeleton directory and then put a set of basic files and directories in it that many projects have. Here's my starter: $ mkdir c-skeleton $ cd c-skeleton/ $ touch LICENSE README.md Makefile $ mkdir bin src tests $ cp dbg.h src/ # this is from Ex20 $ ls -l total 8 -rw-r--r-- 1 zedshaw staff 0 Mar 31 16:38 LICENSE -rw-r--r-- 1 zedshaw staff 1168 Apr 1 17:00 Makefile -rw-r--r-- 1 zedshaw staff 0 Mar 31 16:38 README.md drwxr-xr-x 2 zedshaw staff 68 Mar 31 16:38 bin drwxr-xr-x 2 zedshaw staff 68 Apr 1 10:07 build drwxr-xr-x 3 zedshaw staff 102 Apr 3 16:28 src drwxr-xr-x 2 zedshaw staff 68 Mar 31 16:38 tests $ ls -l src total 8 -rw-r--r-- 1 zedshaw staff 982 Apr 3 16:28 dbg.h $ At the end you see me do an ls -l so you can see the final results. Here's what each of these does: LICENSE If you release the source of your projects you'll want to include a license. If you don't though, the code is copyright by you and nobody has rights to it by default. README.md Basic instructions for using your project go here. It ends in .md so that it will be interpreted as markdown. Makefile The main build file for the project. bin/ Where programs that users can run go. This is usually empty and the Makefile will create it if it's not there. build/ Where libraries and other build artifacts go. Also empty and the Makefile will create it if it's not there. src/ Where the source code goes, usually .c and .h files. tests/ Where automated tests go. src/dbg.h I copied the dbg.h from Exercise 20 into src/ for later. I'll now break down each of the components of this skeleton project so you can understand how it works. ======Makefile====== The first thing I'll cover is the Makefile because from that you can understand how everything else works. The Makefile in this exercise is much more detailed than ones you've used so far, so I'm going to break it down after you type it in: ======CFLAGS=-g -O2 -Wall -Wextra -Isrc -rdynamic -DNDEBUG $(OPTFLAGS)====== ======LIBS=-ldl $(OPTLIBS)====== ======PREFIX?=/usr/local====== ======SOURCES=$(wildcard src/**/*.c src/*.c)====== ======OBJECTS=$(patsubst %.c,%.o,$(SOURCES))====== ======TEST_SRC=$(wildcard tests/*_tests.c)====== ======TESTS=$(patsubst %.c,%,$(TEST_SRC))====== ======TARGET=build/libYOUR_LIBRARY.a====== ======SO_TARGET=$(patsubst %.a,%.so,$(TARGET))====== # The Target Build all: $(TARGET) $(SO_TARGET) tests dev: CFLAGS=-g -Wall -Isrc -Wall -Wextra $(OPTFLAGS) dev: all $(TARGET): CFLAGS += -fPIC $(TARGET): build $(OBJECTS) ar rcs $@ $(OBJECTS) ranlib $@ $(SO_TARGET): $(TARGET) $(OBJECTS) $(CC) -shared -o $@ $(OBJECTS) build: @mkdir -p build @mkdir -p bin # The Unit Tests .PHONY: tests tests: CFLAGS += $(TARGET) tests: $(TESTS) sh ./tests/runtests.sh valgrind: VALGRIND="valgrind --log-file=/tmp/valgrind-%p.log" $(MAKE) # The Cleaner clean: rm -rf build $(OBJECTS) $(TESTS) rm -f tests/tests.log find . -name "*.gc*" -exec rm {} \; rm -rf `find . -name "*.dSYM" -print` # The Install install: all install -d $(DESTDIR)/$(PREFIX)/lib/ install $(TARGET) $(DESTDIR)/$(PREFIX)/lib/ # The Checker ======BADFUNCS='[^_.>a-zA-Z0-9](str(n?cpy|n?cat|xfrm|n?dup|str|pbrk|tok|_)|stpn?cpy|a?====== sn?printf|byte_)' check: @echo Files with potentially dangerous functions. @egrep $(BADFUNCS) $(SOURCES) || true Remember that you need to indent the Makefile consistently with tab characters. Your editor should know that and do the right thing, but if it doesn't then get a different text editor. No programmer should use an editor that fails at something so simple. ======The Header====== This makefile is designed to build a library we'll be working on later and to do so reliably on almost any platform by using special features of GNU make. I'll break down each part in sections, starting with the header. Makefile:1 These are the usual CFLAGS that you set in all of your projects, but with a few others that may be needed to build libraries. You may need to adjust these for different platforms. Notice the OPTFLAGS variable at the end which lets people augment the build options as needed. Makefile:2 Options used when linking a library, and allows someone else to augment the linking options using the OPTLIBS variable. Makefile:3 Setting an optional variable called PREFIX that will only have this value if the person running the Makefile didn't already give a PREFIX setting. That's what the ?= does. Makefile:5 This fancy line of awesome dynamically creates the SOURCES variable by doing a wildcard search for all *.c files in the src/ directory. You have to give both src/**/*.c and src/*.c so that GNU make will include the files in src and also the ones below it. Makefile:6 Once you have the list of source files, you can then use the patsubst to take the SOURCES list of *.c files and make a new list of all the object files. You do this by telling patsubst to change all %.c extensions to %.o and then those are assigned to OBJECTS. Makefile:8 Using the wildcard again to find all the test source files for the unit tests. These are separate from the library's source files. Makefile:9 Then using the same patsubst trick to dynamically get all the TEST targets. In this case I'm stripping away the .c extension so that a full program will be made with the same name. Previously I had replaced the .c with {.o} so an object file is created. Makefile:11 Finally, we say the ultimate target is build/libYOUR_LIBRARY.a, which you will change to be whatever library you are actually trying to build. This completes the top of the Makefile, but I should explain what I mean by "lets people augment the build". When you run make you can do this: # WARNING! Just a demonstration, won't really work right now. # this installs the library into /tmp $ make PREFIX=/tmp install # this tells it to add pthreads $ make OPTFLAGS=-pthread If you pass in options that match the same kind of variables you have in your Makefile, then those will show up in your build. You can then use this to change how the Makefile runs. The first one alters the PREFIX so that it installs into /tmp instead. The second one sets OPTFLAGS so that the -pthread option is present. ======The Target Build====== Continuing with the breakdown of the Makefile I have actually building the object files and targets: Makefile:14 Remember that the first target is what make will run by default when no target is given. In this case it's called all: and it gives $(TARGET) tests as the targets to build. Look up at the TARGET variable and you see that's the library, so all: will first build the library. The tests target is then further down in the Makefile and builds the unit tests. Makefile:16 Another target for making "developer builds" that introduces a technique for changing options for just one target. If I do a "dev build" I want the CFLAGS to include options like -Wextra that are useful for finding bugs. If you place them on the target line as options like this, then give another line that says the original target (in this case all) then it will change the options you set. I use this for setting different flags on different platforms that need it. Makefile:19 Builds the TARGET library, whatever that is, and also uses the same trick from line 15 of giving a target with just options changes to alter them for this run. In this case I'm adding -fPIC just for the library build using the += syntax to add it on. Makefile:20 Now the real target where I say first make the build directory, then compile all the OBJECTS. Makefile:21 Runs the ar command which actually makes the TARGET. The syntax $@ $(OBJECTS) is a way of saying, "put the target for this Makefile source here and all the OBJECTS after that". In this case the $@ maps back to the $(TARGET) on line 19, which maps to build/libYOUR_LIBRARY.a. It seems like a lot to keep track of this indirection, and it can be, but once you get it working this means you just change TARGET at the top and build a whole new library. Makefile:22 Finally, to make the library you run ranlib on the TARGET and it's built. Makefile:24-24 This just makes the build/ or bin/ directories if they don't exist. This is then referenced from line 19 when it gives the build target to make sure the build/ directory is made. You now have all the stuff you need to build the software, so we'll create a way to build and run unit tests to do automated testing. ======The Unit Tests====== C is different from other languages because it's easier to create one tiny little program for each thing you're testing. Some testing frameworks try to emulate the module concept other languages have and do dynamic loading, but this doesn't work well in C. It's also unnecessary because you can just make a single program that's run for each test instead. I'll cover this part of the Makefile, and then later you'll see the contents of the tests/ directory that make it actually work. Makefile:29 If you have a target that's not "real", but there is a directory or file with that name, then you need to tag the target with .PHONY: so make will ignore the file and always run. Makefile:30 I use the same trick for modifying the CFLAGS variable to add the TARGET to the build so that each of the test programs will be linked with the TARGET library. In this case it will add build/libYOUR_LIBRARY.a to the linking. Makefile:31 Then I have the actual tests: target which depends on all the programs listed in the TESTS variable we created in the header. This one line actually says, "Make, use what you know about building programs and the current CFLAGS settings to build each program in TESTS." Makefile:32 Finally, when all of the TESTS are built, there's a simple shell script I'll create later that knows how to run them all and report their output. This line actually runs it so you can see the test results. Makefile:34-35 In order to be able to dynamically rerun the tests with Valgrind there's a valgrind: target that sets the right variable and runs itself again. This puts the valgrind logs into /tmp/valgrind-*.log so you can go look and see what might be going on. The tests/runtests.sh then knows to run the test programs under Valgrind when it sees this VALGRIND variable. For the unit testing to work you'll need to create a little shell script that knows how to run the programs. Go ahead and create this tests/runtests.sh script: echo "Running unit tests:" for i in tests/*_tests do if test -f $i then if $VALGRIND ./$i 2>> tests/tests.log then echo $i PASS else echo "ERROR in test $i: here's tests/tests.log" echo "------" tail tests/tests.log exit 1 fi fi done echo "" I'll be using this later when I cover how unit tests work. ======The Cleaner====== I now have fully working unit tests, so next up is making things clean when I need to reset everything. Makefile:38 The clean: target starts things off whenever we need to clean up the project. Makefile:39-42 This cleans out most of the junk that various compilers and tools leave behind. It also gets rid of the build/ directory and uses a trick at the end to cleanly erase the weird *.dSYM directories Apple's XCode leaves behind for debugging purposes. If you run into junk that you need to clean out, simply augment the list of things being deleted in this target. ======The Install====== After that I'll need a way to install the project, and for a Makefile that's building a library I just need to put something in the common PREFIX directory, which is usually /usr/local/lib. Makefile:45 This makes install: depend on the all: target so that when you run make install it will be sure to build everything. Makefile:46 I then use the program install to create the target lib directory if it doesn't exist. In this case I'm trying to make the install as flexible as possible by using two variables that are conventions for installers. DESTDIR is handed to make by installers that do their builds in secure or odd locations to build packages. PREFIX is used when people want the project to be installed in someplace other than /usr/local. Makefile:47 After that I'm just using install to actually install the library where it needs to go. The purpose of the install program is to make sure things have the right permissions set. When you run make install you usually have to do it as the root user, so the typical build process is make && sudo make install. ======The Checker====== The very last part of this Makefile is a bonus that I include in my C projects to help me dig out any attempts to use the "bad" functions in C. Namely the string functions and other "unprotected buffer" functions. Makefile:50 Sets a variable which is a big regex looking for bad functions like strcpy. Makefile:51 The check: target so you can run a check whenever you need. Makefile:52 Just a way to print a message, but doing @echo tells make to not print the command, just its output. Makefile:53 Run the egrep command on the source files looking for any bad patterns. The || true at the end is a way to prevent make from thinking that egrep not finding things is a failure. When you run this it will have the odd effect that you'll get an error when there is nothing bad going on. ======What You Should See====== I have two more exercises to go before I'm done building the project skeleton directory, but here's me testing out the features of the Makefile. $ make clean rm -rf build rm -f tests/tests.log find . -name "*.gc*" -exec rm {} \; rm -rf `find . -name "*.dSYM" -print` $ make check ======Files with potentially dangerous functions.====== ^Cmake: *** [check] Interrupt: 2 $ make ar rcs build/libYOUR_LIBRARY.a ar: no archive members specified usage: ar -d [-TLsv] archive file ... ar -m [-TLsv] archive file ... ar -m [-abiTLsv] position archive file ... ar -p [-TLsv] archive [file ...] ar -q [-cTLsv] archive file ... ar -r [-cuTLsv] archive file ... ar -r [-abciuTLsv] position archive file ... ar -t [-TLsv] archive [file ...] ar -x [-ouTLsv] archive [file ...] make: *** [build/libYOUR_LIBRARY.a] Error 1 $ make valgrind ======VALGRIND="valgrind --log-file=/tmp/valgrind-%p.log" make====== ar rcs build/libYOUR_LIBRARY.a ar: no archive members specified usage: ar -d [-TLsv] archive file ... ar -m [-TLsv] archive file ... ar -m [-abiTLsv] position archive file ... ar -p [-TLsv] archive [file ...] ar -q [-cTLsv] archive file ... ar -r [-cuTLsv] archive file ... ar -r [-abciuTLsv] position archive file ... ar -t [-TLsv] archive [file ...] ar -x [-ouTLsv] archive [file ...] make[1]: *** [build/libYOUR_LIBRARY.a] Error 1 make: *** [valgrind] Error 2 $ When I run the clean: target that works, but because I don't have any source files in the src/ directory none of the other commands really work. I'll finish that up in the next exercises. ======Extra Credit====== * Try to get the Makefile to actually work by putting a source and header file in src/ and making the library. You shouldn't need a main function in the source file. * Research what functions the check: target is looking for in the BADFUNCS regular expression it's using. * If you don't do automated unit testing, then go read about it so you're prepared later. Copyright (C) 2010 Zed. A. Shaw Credits