Search code examples
linuxmakefile

How to safely reference directories in Makefile to prevent `rm -fr /*`?


Here is the standard way I usually define clean target in hand-written Makefiles:

clean:
    rm -fr $(BUILD_DIR)/*

If there is a typo in the variable or I forgot to define it, it will run rm -rf /* instead, which is not a risk I want to accept. But after having an accident when exactly this happened, I've been trying to find a better way to reference paths in Makefile in a safe way and I couldn't find it.

Is there a better way?


Solution

  • Let's start with there are many, many ways to structure a Makefile to make things easier to clean. Most are by personal preference, and therefore not a right or wrong issue.

    In your case, the thing that stands out is the attempt to cleanup with a single rm command under the clean: phony target. It is far easier to use multiple specific rm commands rather than one "encompasses all" rm -r. As you have found, a misnamed or absent variable setting can have dire consequences when you rm -r $(SOMEVAR)/*.

    What I generally do is put sources in the src subdirectory, includes in the include subdirectory and then have all object files output to an obj subdirectory. (you can also have binaries created in a bin directory if you like). In that case clean: becomes

    clean:
        rm -rvf $(OBJ)
        rm -rvf $(BIN)
    

    An easy setup that allows you to do just that is:

    TARGET  = myexe
    
    CC  = gcc
    
    BIN     = bin
    SRC     = src
    OBJ     = obj
    SRCS    = $(wildcard $(SRC)/*.c)
    OBJS    = $(patsubst $(SRC)%.c,  $(OBJ)/%.o, $(SRCS))
    
    CFLAGS  += -Wall -Wextra -pedantic -Wshadow -Werror -std=c11
    CFLAGS  += -Iinclude
    LDFLAGS +=
    
    # combines clean and create directories before build
    all:    clean setup $(TARGET)
    
    # create directories
    setup:
        @mkdir -p $(BIN)
        @mkdir -p $(OBJ)
    
    # build executable
    $(TARGET):  $(OBJS)
        $(CC) $(CFLAGS) $(LDFLAGS)  -o $(BIN)/$(TARGET) $^
    
    # compile objects
    $(OBJ)/%.o: $(SRC)/%.c
        $(CC) -c $(CFLAGS) $< -o $@
    
    # clean
    clean:
        @rm -rvf $(OBJ)
        @rm -rvf $(BIN)
    

    (note: shown for building C sources, adjust extensions as needed)

    This way you never have the potential to rm -rf $(SOMEVAR)/* when SOMEVAR is undefined or empty. Good luck with your coding.