1. 26
  1.  

  2. 10

    This template:

    1. Doesn’t let the user specify a C compiler,
    2. Overrides CFLAGS from the system defaults/environment,
    3. Overrides LDFLAGS from the system defaults/environment.

    Project-specific CFLAGS and LDFLAGS should be set with += and the C compiler should be invoked with $(CC).

    Additionally, I find generating a .depend file much cleaner than littering .d files:

    depend: $(SRCS)
    	$(CC) $(CFLAGS) -MM $(SRCS) > .depend
    
    -include .depend
    
    1. 8

      After reading a few comments on the thread, we can understand why it’s so hard to write “good makefiles”.

      The makefike presented in this post is really simple, and make use of a “trick” (gcc dependency graph) to make it smarter. Then we have people advising to add this or that, to make it even more “automatic”, at the cost of making the makefile itself more complex and less portable.

      To me, a good makefile is a simple one that doesn’t use any tricks, so I can easily understand how it works, and can tweak some settings using standard variables, like LDFLAGS or CC.

      IMO, this is by trying to trick the makefiles to be always smarter and automatic that we ended up in the mess it currently is with cmake and autotools.

      1. 7

        “trick” (gcc dependency graph)

        Fun fact, Ninja also uses that. That’s the only way to get header dependencies right (other than forcing the user to list them all manually or parsing includes i.e. reinventing the compiler)

        1. 2

          All serious build tools do it, because it’s the right thing to do. The best way to do this is to touch depfiles on the first pass, then overwrite them as a side-effect of compilation. (This is what automake does.)

      2. 7
        1. Couldn’t copy and paste this Makefile, because I had to change spaces to tabs manually. There were several syntax errors right after pasting it.

        2. It mixes source code with autogenerated files.

        3. Doesn’t track headers from SDK.

        4. Doesn’t support Visual Studio’s compiler.

        5. Doesn’t support Debug/Release build.

        6. Doesn’t let the user to specify their own CFLAGS or LDFLAGS.

        7. Doesn’t detect compiler version change, can lead to invalid builds if this will happen.

        CMake on the other hand supports everything that this Makefile supports, plus the few points above, with less lines of configuration:

        cmake_minimum_required(VERSION 3.5)
        add_executable(main foo.c bar.c main.c)
        set_property(TARGET main PROPERTY C_STANDARD 99)
        target_link_libraries(main m)
        

        It works for all compilers, and the user can generate makefiles if they want command line build using ‘make’, also Eclipse project if they want IDE support, Visual Studio solution if they’re on Windows, Xcode if they’re on Mac, etc.

        1. 5

          Not using $CC? Using $LDFLAGS for required libraries is a bit annoying, let users use it to specify their own flags without having to copy all the required libraries out of your makefile.

          And its also good to name it GNUmakefile if you require GNU features.

          1. 2

            That ship has sailed. GNU make is the practical standard. Do you name your shell scripts that non-POSIX bash features .bash instead of .sh? Should the $CFLAGS be named $GCCFLAGS?

            1. 1

              I don’t see a problem with using GNUmakefile if you have the choice, it has no negative side effects afaik. File extensions are different, they are useless anyways, we use shebangs and yes non-POSIX shell scripts with a #!/bin/sh shebang are really really bad, 100x worse than the makefile situation.

            2. 1

              And its also good to name it GNUmakefile if you require GNU features.

              I suppose it technically is gnu-specific… The only things I can see are -include, which is not included in the standard, along with % rules. FreeBSD appears to use GNU make. NetBSD supports -include syntax, though it doesn’t appear to support pattern rules (though it’s been on the todo-list for almost four years). Pattern rules are very useful, though this makefile could be rewritten to use suffix rules instead, though it is not very idiomatic. There is also a proposal to add them to posix, but it appears to not have been updated since 2011.

              edit: it appears -include is standard posix as well, leaving only pattern rules

              1. 5

                FreeBSD appears to use GNU make.

                FreeBSD does not use GNU Make[1]. You can install Gnu Make as gmake via packages.

                [1] https://www.freebsd.org/cgi/man.cgi?make(1)

                1. 1

                  Ah, I guess I misread that.

                  I’m kindof surprised at how many implementations don’t have pattern rules given how useful they are out of the basic functionality shown in OP’s makefile.

            3. 4

              I would recommend changing your libraries flags to LDLIBS and leave LDFLAGS for true linker flags. Then your final compilation line can be $(CC) $(OBJS) $(LDFLAGS) $(LDLIBS)-o $@. You should also use += when changing these sorts of variables so that you can specify additional flags and libraries on the command line.

              Another thing you can do is use pkg-config to determine the library CFLAGS and LIBS. I use something like this:

              PC_DEPS := sdl2
              PC_CFLAGS := $(shell pkg-config --cflags $(PC_DEPS))
              PC_LIBS := $(shell pkg-config --libs $(PC_DEPS))
              
              INCS := $(addprefix -I,$(shell find ./include -type d))
              
              CFLAGS += $(PC_CFLAGS) $(INCS) -MMD -MP -pedantic -pedantic-errors -std=c89
              LDLIBS += $(PC_LIBS) -lm
              
              .syntastic_c_config:
                      echo $(CFLAGS) | tr ' ' '\n' > .syntastic_c_config
              
              ...
              
              1. 3

                Lots more detail in this other blog post: http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/#tldr

                I’d never heard of gcc -MF before so this is pretty cool …

                1. 2

                  This is nice. The best Makefiles are nearly empty and make heavy use of templates and implicit rules. I would make a couple small changes:

                  1. I’m not sure why the target that generates dependency Makefile fragments renames the generated file. This should work:

                    %.d: %.c Makefile
                    $(CPP) $(CPPFLAGS) -M -MM -E -o “$@” “$<”

                  2. You might want to prevent generating Makefile fragments for the clean goal. A conditional include can help:

                    ifneq ($(MAKECMDGOALS),clean)
                    -include $(DEPS)
                    endif

                  3. Remaking target objects if the Makefile changes can be simply:

                    $(OBJS): Makefile

                  1. 3

                    While I also do use templates and implicit rules when convenient (your example is certainly one of these), my experience is that Makefiles are best when they try not to be clever, and simply define straightforward from->to rules with no room for subtlety. As an example, make treats some of the files produced through chains of implicit rules as temporary, and will delete them automatically. In some cases, I have found this will cause spurious rebuilds. There is some strangely named variable you can set to avoid this deletion, but I’d rather such implicit behaviour be opt-in than opt-out.

                    Sometimes a little duplication is better than a little magic.

                    1. 3

                      Yes, the special target .PRECIOUS can be used to mark intermediate files that should be kept. Cf. https://www.gnu.org/software/make/manual/make.html#index-_002ePRECIOUS-intermediate-files

                      My recommendation for anyone who wants to learn to effectively use make: Read the manual. All of it. Keep it handy when writing your Makefile.

                      People have already done the hard work of getting it to work right under most circumstances. I don’t consider it clever to stand on their shoulders.

                  2. 2

                    in addition to what @jec mentioned, -MMD already implies -MF. -MT is unnecessary because you are generating the targets from the sources in the same way that gcc would. all should be phony as well. Not sure why you have an empty rule for %.d. Those files are not required by anything, and the default behaviour (building everything) is fine for the first build.You can also use $(CC) instead of gcc so (for example) the user could change the compiler. This is stylistic in this case, but I like to assign all my variable using := where possible. This prevents something like MACHINE := $(shell uname -m) from being evaluated multiple times. Of course, you still need to use = when creating functions (or other times when you do want your variable to be evaluated multiple times).

                    1. 2

                      Really like the template, always good to remember the -M directives with GCC/Clang

                      My one recommendation is to fix the usage of CFLAGS and LDFLAGS. I would recommend against putting CFLAGS in your linking rule ($(BIN): $(OBJS)) since all of your CFLAGS there are for compiling stage only. Additionally, this means if you want to add -flto then you should put them in LDFLAGS, since that variable is traditionally use for linker stage variables.

                      1. 2

                        I think the amount of discussion about the hows and whys and portability of small things like resolving dependencies is a good argument for using something like Meson, at least for non-trivial projects:

                        project('foobar', 'c')
                        cc = meson.get_compiler('c')
                        m_dep = cc.find_library('m', required : false)
                        executable('foobar', 'main.c', 'foo.c', 'bar.c', dependencies : m_dep)