Search code examples
cmakefiledependenciesinclude

Makefile error when including dependency files generated from another Makefile


I have the current structures of directories:

  • ./myFolder/
    • Makefile
    • main.c
  • ./common/
    • Makefile
    • error.c
    • error.h
  • ./build/
    • /common/
    • /myFolder/

/build/common contains the object and dependency files obtained from the compilation of the folder common and the equivalent for build/myFolder.

Basically /myFolder/ contains a programme that requires some function contained in common. Thus, the /myFolder/Makefile calls also the /common/Makefile to compile.

./common/Makefile is the following:

CC = gcc

CURRENT_DIR := $(shell basename $(CURDIR))
SOURCEDIR   := ./
SOURCES     := $(wildcard $(SOURCEDIR)/*.c)
OBJDIR      := ../build/$(CURRENT_DIR)

OBJECTS     := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.o, $(SOURCES))
DEPENDS     := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.d, $(SOURCES))

# ADD MORE WARNINGS!
WARNING := -Wall -Wextra

all: $(OBJECTS)

clean:
    $(RM) $(OBJECTS) $(DEPENDS) $(EXECUTABLE)

-include $(DEPENDS) 

$(OBJDIR)/%.o: $(SOURCEDIR)/%.c Makefile | $(OBJDIR)
    $(CC) $(WARNING) -MMD -MP -c $< -o $@

$(OBJDIR):
    mkdir -p $(OBJDIR)

and ./myFolder/Makefile is the following:

CC = gcc

INC_PATH       := -I../common/
BUILD_DIR_NAME := build
BUILDDIR       := ../$(BUILD_DIR_NAME)
CURR_DIR_NAME  := $(shell basename $(CURDIR))
SOURCEDIR      := ./
SOURCES        := $(wildcard $(SOURCEDIR)/*.c)
OBJDIR         := $(BUILDDIR)/$(CURR_DIR_NAME)
OBJECTS        := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.o, $(SOURCES))
DEPENDS        := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.d, $(SOURCES))

COMMONDIR_NAME := common
COMMONDIR      := ../$(COMMONDIR_NAME)
SOURCESCOMMON  := $(wildcard $(COMMONDIR)/*.c)
OBJDIRCOMMON   := $(BUILDDIR)/$(COMMONDIR_NAME)
OBJECTSCOMMON  := $(patsubst $(COMMONDIR)/%.c,$(OBJDIRCOMMON)/%.o, $(SOURCESCOMMON))
DEPENDSCOMMON  := $(patsubst $(COMMONDIR)/%.c,$(OBJDIRCOMMON)/%.d, $(SOURCESCOMMON))

# ADD MORE WARNINGS!
WARNING := -Wall -Wextra

EXECUTABLE := ../out_file

# .PHONY means these rules get executed even if
# files of those names exist.
.PHONY: all clean

# The first rule is the default, ie. "make",
# "make all" and "make parking" mean the same
all: $(EXECUTABLE)

clean:
    $(RM) $(OBJECTS) $(DEPENDS) $(EXECUTABLE)
    +$(MAKE) -C $(COMMONDIR) clean

# Linking the executable from the object files
# $^   # "src.c src.h" (all prerequisites)
$(EXECUTABLE): $(OBJECTS) $(OBJECTSCOMMON)
    $(CC) $(WARNING) $^ -o $@

-include $(DEPENDS) $(DEPENDSCOMMON)

$(OBJDIR):
    mkdir -p $(OBJDIR)

$(OBJDIR)/%.o: $(SOURCEDIR)/%.c Makefile | $(OBJDIR)
    $(CC) $(WARNING) -MMD -MP -c $(INC_PATH) $< -o $@

$(OBJDIRCOMMON):
    mkdir -p $(OBJDIRCOMMON)

$(OBJDIRCOMMON)/%.o: $(COMMONDIR)/%.c $(COMMONDIR)/Makefile| $(OBJDIRCOMMON)
    +$(MAKE) -C $(COMMONDIR)

If I step into the folder myFolder and compile with make for the first time, everything is ok. If then I try again I get the following error:

make: *** No rule to make target `error.c', needed by `../build/common/error.o'.  Stop.

If I do make clean and then make it works.

The line causing the problem is the following:

-include $(DEPENDS) $(DEPENDSCOMMON)

particularly $(DEPENDSCOMMON). With -include $(DEPENDS) instead of it, it works perfectly.

Now, - tells the Makefile not to complain if the files do not exist. If they exist they will be included and recompile your sources properly according to the dependencies. For this reason I would expect not to compile at the first attempt, but if it compiles at the first attempt, also to not generate such error (since all files exist).

Can someone please tell me what I am missing?

For completeness here are the other files which are just examples:

main.c

#include "../common/error.h"
#include <stdio.h>


int main(int argc, char *argv[])
{
    if (argc<1) err_sys("some error");
    printf("Hello world\n");
    return 0;
}

error.c

#include <stdio.h>

void err_sys(const char *fmt, ...)
{
    printf("some err\n");
}

error.h

#ifndef _ERROR_H_
#define _ERROR_H_
#endif

/*Fatal error related to a system cal1.
* Print a message and terminate*/
void err_sys(const char *fmt,...);

Solution

  • The error is related to your Makefile structure.

    The main problem is that you generate the dependencies in common/Makefile and use it in myFolder/Makefile. The dependencies are generated for $(SOURCEDIR)/%.c, e.g. ./somefile.c which is valid only in common, not in myFolder

    A related problem is that you duplicate many parts from common/Makefile in myFolder/Makefile. It doesn't make much sense to have a separate Makefile in this situation.

    One way to solve this would be to build a library from the files in common and reference only the library from myFolder/Makefile. This avoids duplicating information from common/Makefile to myFolder/Makefile. I changed your Makefiles to work this way. (There might be room for improvement, I only wanted to make it work.)

    common/Makefile:

    CC = gcc
    
    CURRENT_DIR := $(shell basename $(CURDIR))
    SOURCEDIR   := ./
    SOURCES     := $(wildcard $(SOURCEDIR)/*.c)
    OBJDIR      := ../build/$(CURRENT_DIR)
    
    LIBNAME=libcommon.a
    COMMONLIB = $(OBJDIR)/$(LIBNAME)
    
    OBJECTS     := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.o, $(SOURCES))
    DEPENDS     := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.d, $(SOURCES))
    
    .PHONY: all clean
    
    # ADD MORE WARNINGS!
    WARNING := -Wall -Wextra
    
    all: $(COMMONLIB)
    
    clean:
            $(RM) $(OBJECTS) $(DEPENDS) $(COMMONLIB)
    
    -include $(DEPENDS)
    
    $(OBJDIR)/%.o: $(SOURCEDIR)/%.c Makefile | $(OBJDIR)
            $(CC) $(WARNING) -MMD -MP -c $< -o $@
    
    $(OBJDIR):
            mkdir -p $(OBJDIR)
    
    
    $(COMMONLIB): $(OBJECTS)
            $(AR) $(ARFLAGS) $@ $^
    

    myFolder/Makefile:

    CC = gcc
    
    INC_PATH       := -I../common/
    BUILD_DIR_NAME := build
    BUILDDIR       := ../$(BUILD_DIR_NAME)
    CURR_DIR_NAME  := $(shell basename $(CURDIR))
    SOURCEDIR      := ./
    SOURCES        := $(wildcard $(SOURCEDIR)/*.c)
    OBJDIR         := $(BUILDDIR)/$(CURR_DIR_NAME)
    OBJECTS        := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.o, $(SOURCES))
    DEPENDS        := $(patsubst $(SOURCEDIR)/%.c,$(OBJDIR)/%.d, $(SOURCES))
    
    COMMONDIR_NAME := common
    COMMONDIR      := ../$(COMMONDIR_NAME)
    
    OBJDIRCOMMON   := $(BUILDDIR)/$(COMMONDIR_NAME)
    
    LIBNAME=libcommon.a
    COMMONLIB = $(OBJDIRCOMMON)/$(LIBNAME)
    
    
    .PHONY: buildlib clean all
    
    # ADD MORE WARNINGS!
    WARNING := -Wall -Wextra
    
    EXECUTABLE := ../out_file
    
    # .PHONY means these rules get executed even if
    # files of those names exist.
    .PHONY: all clean
    
    # The first rule is the default, ie. "make",
    # "make all" and "make parking" mean the same
    all: $(EXECUTABLE)
    
    clean:
        $(RM) $(OBJECTS) $(DEPENDS) $(EXECUTABLE)
        +$(MAKE) -C $(COMMONDIR) clean
    
    # Linking the executable from the object files
    # $^   # "src.c src.h" (all prerequisites)
    $(EXECUTABLE): $(OBJECTS) $(COMMONLIB)
        $(CC) $(WARNING) $^ -o $@
    
    -include $(DEPENDS)
    
    $(OBJDIR):
        mkdir -p $(OBJDIR)
    
    $(OBJDIR)/%.o: $(SOURCEDIR)/%.c Makefile | $(OBJDIR)
        $(CC) $(WARNING) -MMD -MP -c $(INC_PATH) $< -o $@
    
    $(COMMONLIB): FORCE
        +$(MAKE) -C $(COMMONDIR)
    
    FORCE:
    

    Note: I used the target FORCE with no rule as a dependency instead of declaring $(COMMONLIB) as .PHONY to force running the recursive make. When I tried with .PHONY, the executable was re-built every time without checking the time stamp of the library file.


    Another option would be to throw away the Makefile in common and replace the recursive make call in myFolder/Makefile with the corresponding compile command.

    $(OBJDIRCOMMON)/%.o: $(COMMONDIR)/%.c Makefile| $(OBJDIRCOMMON)
        $(CC) $(WARNING) -MMD -MP -c $< -o $@
    

    Notes

    You don't need to use the relative path ../common in the #include directive when you specify this as an include directory with option -I.

    I added .PHONY to declare your phony targets as such.

    For automatic dependency generation and general recommendations about Makefiles I recommend to read the papers at http://make.mad-scientist.net/papers/.

    You might also consider using a Makefile generator like cmake.

    If you copy&paste the Makefile code from here, make sure to replace the spaces at the beginning of the lines with a tab.