9
\$\begingroup\$

This is a makefile I wrote to compile a simple OpenGL test project which is going to use the libraries GLFW and the C library glad.c.

CC = gcc
CXX = g++

COMPILE_FLAGS = -Wall -ggdb -O3
LINK_FLAGS = -lglfw3 -lopengl32 -lglu32 -lgdi32

glfw = d:/external/glfw-3.1
glfw_inc = $(glfw)/include
glfw_lib = $(glfw)/lib64

glad = d:/external/glad-c
glad_inc = $(glad)/include

INCLUDES = -I$(glfw_inc) -I$(glad_inc)
LIBRARIES = -L$(glfw_lib)

cpp_files = main.cpp
objects = $(cpp_files:.cpp=.o)
headers =

all: main.exe

main.exe: $(objects) glad.o
        $(CXX) $(LIBRARIES) -o main.exe $(objects) glad.o $(LINK_FLAGS)

$(objects): %.o: %.cpp $(headers) makefile
        $(CXX) $(COMPILE_FLAGS) $(INCLUDES) -c -o $@ $<

glad.o: glad.c
        $(CC) $(COMPILE_FLAGS) $(INCLUDES) -c -o glad.o glad.c

Since this is basically the first makefile I wrote, I could surely use some criticism as to what is good style and what isn't. I also wonder if there is a more elegant way to handle the fact that glad is actually a C library and has to be compiled with gcc instead of g++, but g++ is used for everything else.

\$\endgroup\$
2
  • \$\begingroup\$ I've restored the original title as it's actually a more suitable one for this site. "First makefile: criticism wanted" is too generic. \$\endgroup\$
    – Jamal
    Jan 28, 2015 at 13:38
  • \$\begingroup\$ You're compiling with debug symbols enabled (-ggdb), so I think it would be better to disable optimizations to make sure very debuggable code is generated. I.e.: replace -O3 with -O0. Also, define some kind of flag to disable debugging on a "release" build. \$\endgroup\$
    – glampert
    Jan 28, 2015 at 17:23

2 Answers 2

8
\$\begingroup\$

First of all, good for you for learning how to write a proper makefile. Few people are any good at it, and it's a really fundamental tool. Even when nice tools such as CMake are used, it's very useful to understand how to write one from scratch.

With that said, here are some things that may help you improve your makefile:

Prefer Makefile to makefile

The names are equivalent, but since you're using GNU Make, you may as well follow its conventions, and one convention is to name it Makefile instead of makefile. Either will work, but quoting from the GNU Make man page:

We recommend 'Makefile' because it appears prominently near the beginning of a directory listing, right near other important files such as 'README'.

Use standard variable names

There are standard names for some of the variables you're using. For example, use CFLAGS instead of COMPILE_FLAGS and CXXFLAGS which is the same thing, but for invocations of g++ (or more accurately invocations of $(CXX)). Similarly, use LDFLAGS instead of LINK_FLAGS.

Add INCLUDES to CFLAGS

It's good practice to use variables like INCLUDES to keep things tidy, but it would be better if you added that to CFLAGS:

CFLAGS += $(INCLUDES)

The reason for doing that is in the next point.

Use the implicit rules

GNU Make already has an implicit rule for creating .o files from .c files. That rule looks like this:

 %.o : %.c
         $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@

The result of this is that if you have defined the flags correctly, you can omit an explicit rule and rely instead on the implicit rule. In this particular Makefile, it means that you can entirely omit the rule for glad.o. As long as glad.c is in the current directory, and main.cpp is in the current directory, the built-in rules will understand how to create both glad.o using gcc and main.o using g++.

To expand this a bit, here's what happens when I run make -d to get it to emit more detailed debugging information:

  Considering target file 'main.exe'.
    Considering target file 'main.o'.
     Looking for an implicit rule for 'main.o'.
     Trying pattern rule with stem 'main'.
     Trying implicit prerequisite 'main.c'.
     Trying pattern rule with stem 'main'.
     Trying implicit prerequisite 'main.cc'.
     Trying pattern rule with stem 'main'.
     Trying implicit prerequisite 'main.C'.
     Trying pattern rule with stem 'main'.
     Trying implicit prerequisite 'main.cpp'.
     Found an implicit rule for 'main.o'.
      Considering target file 'main.cpp'.

As you can see, after it has identified a needed target (main.o) it starts looking for implicit rules to create that target. It finds one eventually because main.cpp exists. To see the full list in order on your machine, create an empty subdirectory, navigate to it and execute make -d foo.o. Since foo.o won't exist (it's an empty subdirectory) it will attempt to find an implicit rule that will allow it to create that file and with the -d option, it will tell you all of the things it's trying. On my machine, that generates 734 lines of output, so there's really quite a number of implicit rules. There are rules to create object files from C and C++, but also from Fortran, Pascal, Modula, assembly language, and for trying to check out a .o file from various version control systems.

Add LIBRARIES to LDFLAGS

Your main rule (pardon the pun) does this:

$(CXX) $(LIBRARIES) -o main.exe $(objects) glad.o $(LINK_FLAGS)

There are a number of changes I'd make to that line, but one is to consolidate $(LIBRARIES) and $(LINK_FLAGS) into a single variable with a standard name, which is LDFLAGS.

Use predefined variables

Try to avoid repeating hard-coded names in the production rules. For example, the rule for main.exe could be much simplified to this:

$(CXX) -o $@ $^ $(LDFLAGS)

Add a clean psuedotarget

It's often useful (not least when testing makefiles!) to provide a clean psuedotarget to remove all of the files that were generated, providing a "clean slate" from which to start. It might be written like this:

.PHONY : clean
clean :
        -rm main.exe $(objects) glad.o

This also makes it less likely that you'll want to have the makefile itself as a dependency, which makes the set of rules cleaner. This would work even if the first line were omitted, unless there were actually a file named clean. To tell the makefile that it's not a real file, we use the line .PHONY : clean.

Add glad.o to objects

If you add glad.o to the objects list, you can simplify the rules.

Consider isolating TARGET

For Windows, executables typically end in .exe but not so for most other platforms. For that reason you could either have a specific variable for the executable extension or keep the target name in a variable:

TARGET = main.exe

Putting it all together

If we take all of these suggestions and apply them, the resulting files looks like this:

CC = gcc
CXX = g++

INCLUDES = -I$(glfw_inc) -I$(glad_inc)
LIBRARIES = -L$(glfw_lib) 

glfw = d:/external/glfw-3.1
glfw_inc = $(glfw)/include
glfw_lib = $(glfw)/lib64

glad = d:/external/glad-c
glad_inc = $(glad)/include

CFLAGS = -Wall -ggdb -O3 $(INCLUDES)
CXXFLAGS = -Wall -ggdb -O3 $(INCLUDES)
LDFLAGS = $(LIBRARIES) -lglfw3 -lopengl32 -lglu32 -lgdi32

TARGET = main.exe
cpp_files = main.cpp
objects = $(cpp_files:.cpp=.o) glad.o
headers =

all: $(TARGET)

$(TARGET): $(objects) 
        $(CXX) -o $@ $^ $(LDFLAGS)

.PHONY : clean
clean :
        -rm $(TARGET) $(objects)
\$\endgroup\$
4
  • \$\begingroup\$ Hi Edward and thanks a lot, these are some great tips! But what should I do about my rule for $(objects)? It's missing in your resulting file, but it won't work like this (Nothing to be done for 'all'). But when I just added it like the way it way, it fails because it's looking for glad.cpp, which doesn't exist. This happens because we put the glad.o into objects, but how to work around this successfully? \$\endgroup\$ Jan 28, 2015 at 15:31
  • \$\begingroup\$ Also, could you comment on the pseudo target .PHONY. I don't understand this part at all. Couldn't I just create the clean target and call make clean manually instead? \$\endgroup\$ Jan 28, 2015 at 15:32
  • \$\begingroup\$ I've added some details to address all of those questions. Let me know if it's still unclear. \$\endgroup\$
    – Edward
    Jan 28, 2015 at 15:46
  • \$\begingroup\$ Ah, I guess I could omit the target $(objects) in this case, and make separate targets for the .cpp and the .c parts (kind of like my initial one, but with a variable for all .o files this time to simplify the link command). \$\endgroup\$ Jan 28, 2015 at 16:52
1
\$\begingroup\$

First of all, you should check if you want to use a more modern tool like CMake, which enables you to skip most of the boilerplate.

Also, you shouldn't hard code the file extension .exe unless you really want to write a DOS/Windows-only program (you usually don't want this). A common variable name for that purpose is $(EXEEXT) which is either empty (for almost all operation systems) or ".exe" (for DOS and Windows).

Apart from that, your Makefile looks clean to me.

\$\endgroup\$
5
  • \$\begingroup\$ What "boiler plate" here specifically would CMake avoid? What would a cmake equivalent look like? \$\endgroup\$ Jan 28, 2015 at 16:05
  • 2
    \$\begingroup\$ Disagree. CMAke is just another tool. It was written be people who did not understand make and wanted a simpler tool. Which is fine in its own right. But it is not any better/worse than make it is just a different tool. Personally I would say it has a lot less features and thus a less powerful tool and thus I usually use make. But I have a big generic makefile of the rules I want already defined so my make files are now only a couple of lines long. github.com/Loki-Astari/ThorsSerializer/blob/master/Json/… \$\endgroup\$ Jan 29, 2015 at 19:01
  • 1
    \$\begingroup\$ @CrappyExperienceBye: While I agree that CMake is another tool, I believe they understand Make quite well. However, CMake is not a replacement for make, but rather a replacement for autoconf+automake+make. \$\endgroup\$
    – vog
    Jan 30, 2015 at 10:10
  • \$\begingroup\$ @vog: OK I agree that I was being overly verbostic (and condesending) with who did not understand make. \$\endgroup\$ Jan 30, 2015 at 19:01
  • 1
    \$\begingroup\$ @vog: Though I believe that CMake might be trying to replace autotools I don't believe they are close to that kind of complexity or power. \$\endgroup\$ Jan 30, 2015 at 19:03

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.