1. 9

I went through the process of actually learning how to make a Makefile a while ago, and it wasn’t a very fun experience. There was very little information about how to actually make a good Makefile for a C project; there were lots of tiny examples, and a lot of theory about how make works, but very little for people who just want a Makefile for C (or C++) where you don’t manually have to specify exactly which files depend on which other files, but still only recompile the necessary files.

Because of that, I decided to make a general Makefile which is configured through a config file, to work as a basis for both my future C projects, and for new C or C++ programmers.

I’m not sharing this because it’s some marvelous feat of software engineering, but because it’s something I would have wanted to find while trying to figure this stuff out for myself.

  1.  

  2. 4

    At suckless, we also use a config.mk approach.

    I’m not a fan of the proposed solution by the author, as it uses GNU extensions unnecessarily and is thus non-portable.

    Let me give you an example for a suckless Makefile, which is POSIX compliant, below. It has the same functionality as the proposed solution, modulo unnecessary debug targets you better solve using a -DDEBUG and adding -g to LDFLAGS, but is much shorter. It also allows you to easily create a tarball using “make dist” and handles manuals as well. It allows easy extension, for instance adding MAN5 if you have section 5 manpages and generally uses the naming convention that has established over the years (LDFLAGS, LDLIBS instead of LINK).

    To make it clear: This is not an attack against the author, but I’m sick of seeing ugly Makefiles in the wild. I hope this can be of inspiration for some people here.

    config.mk:

    # example-program version
    VERSION = 1
    
    # Customize below to fit your system
    
    # paths
    PREFIX = /usr/local
    MANPREFIX = ${PREFIX}/man
    
    # flags
    CPPFLAGS = -D_DEFAULT_SOURCE
    CFLAGS   = -std=c99 -pedantic -Wall -Wextra -Os
    LDFLAGS  = -s
    LDLIBS  = -lpng
    
    # compiler and linker
    CC = cc
    

    Makefile:

    # example-program
    # See LICENSE file for copyright and license details.
    .POSIX:
    
    include config.mk
    
    TARG = example-program
    HDR = header1.h header2.h header3.h
    SRC = src1.c src2.c src3.c
    EXTRA = LICENSE README
    MAN1 = $(TARG:=.1)
    
    OBJ = $(SRC:.c=.o)
    
    all: $(TAR)
    
    $(TAR): config.mk $(OBJ)
    	$(CC) -o $@ $(LDFLAGS) $(OBJ) $($LDLIBS)
    
    $(OBJ): config.mk $(HDR)
    
    .c.o:
    	$(CC) -c $(CPPFLAGS) $(CFLAGS) $<
    
    clean:
    	rm -f $(TARG) $(OBJ)
    
    dist:
    	rm -rf "$(TAR)-$(VERSION)"
    	mkdir -p "$(TAR)-$(VERSION)"
    	cp -R Makefile config.mk $(EXTRA) $(HDR) $(SRC) $(MAN1) "$(TAR)-$(VERSION)"
    	tar -cf - "$(TAR)-$(VERSION)" | gzip -c > "$(TAR)-$(VERSION).tar.gz"
    	rm -rf "$(TAR)-$(VERSION)"
    
    install: all
    	mkdir -p "$(DESTDIR)$(PREFIX)/bin"
    	cp -f $(TARG) "$(DESTDIR)$(PREFIX)/bin"
    	for f in $(TARG); do chmod 755 "$(DESTDIR)$(PREFIX)/bin/$$f"; done
    	mkdir -p "$(DESTDIR)$(MANPREFIX)/man1"
    	cp -f $(MAN1) "$(DESTDIR)$(MANPREFIX)/man1"
    	for m in $(MAN1); do chmod 644 "$(DESTDIR)$(MANPREFIX)/man1/$$m"; done
    
    uninstall:
    	for f in $(TARG); do rm -f "$(DESTDIR)$(PREFIX)/bin/$$f"; done
    	for m in $(MAN1); do rm -f "$(DESTDIR)$(MANPREFIX)/man1/$$m"; done
    
    1. 1

      While that’s a neat Makefile, any change in any header will recompile the whole project, which isn’t desirable. It also pollutes by putting all object files in the same directory as the source files, which I personally dislike, but that’s more a matter of preference.

      I will add targets for dist/install/uninstall.

      I’m interested in making it more POSIX compliant. I’ll see what I can do about that.

      1. 1

        Well, if you want to fine-grain you header management, you can always replace

        $(OBJ): config.mk $(HDR)
        

        with the explicit listing of dependencies.

        If you want to make it posix compliant, start off with removing the pattern-substitution rules that use “%”. It’s non-standard.

        1. 1

          Is there a way to do an out-of-tree build with just a POSIX compliant Makefile? I’ve been on the lookout for that for quite a while.

          1. 1

            The typical approach would probably be to copy the makefile into the build directory.

      2. 3

        I’m not sure I agree with the appeal of abstracting out simple Makefiles. The Makefile documentation may suck, but adding a layer of indirection onto a simple Makefile makes me think that “woah, there’s some build wizardry going on!” vs just writing out the Makefile.

        I tend to be a minimalist with technology though.

        1. 2

          My solution-rant: http://eigenstate.org/notes/makefiles

          This solution looks like something close to what I did, but has a few issues where it tramples on conventions (overriding $(CC)) and some reliability issues (doesn’t generate dependencies).

          1. 1

            I’m not entirely sure whether you mean interdependencies between files (e.g both foo.c and bar.c depend on baz.h and should be recompiled when baz.h changes) or if you mean specifying external files to make sure you recompile when those change, but this makefile supports both of those; https://github.com/mortie/easy-makefile/blob/4c9a64bbb1cfd511030ac220bfdd79bf84c8c676/Makefile#L94 takes care of the interdependencies between files in the project, and there’s a DEPS-variable for dependencies. I should probably add something like LIBS or STATIC_LINK for statically linked libraries though, which both adds the files to the final compile command to produce the binary, and adds them as dependencies.

            You’re also right that I shouldn’t use CC like I do, and it felt dirty, but I left it in as I didn’t come up with anything better. I should probably look at the extension, and use $(CC) if it’s .c and $(CXX) if it’s .cc.

            Your write-up looks really informative though. I’ll read through it and see if I can follow conventions more closely in more places too.

            1. 1

              Ok, I just pushed a commit: https://github.com/mortie/easy-makefile/commit/410ef51d98842c4705bccf5e342c657a2f1467dd - it now no longer overwrites CC or CXX, it respects CFLAGS and CXXFLAGS, and you can list statically linked dependencies, and changes to those dependencies will recompile the binary. Does that look ok, and is there anything more I should consider?

              1. 1

                Looks pretty good to me! I did manage to miss the dep generation when reading it the first time.