0

I have a Makefile defining a variable dependent target like so:

SRCFILES=./src/main.c ./src/file.c
# Get the filenames of all the sources
SRCNAMES=$(notdir $(SRCFILES))

OBJDIR=./obj/
# Replace .c to .o in all source names
OBJNAMES=$(SRCNAMES:.c=.o)
# Add the obj/ repository as prefix
OBJFILES=$(addprefix $(OBJDIR), $(OBJNAMES))

NAME=myprog

all: $(NAME)

$(NAME): $(OBJFILES)
    gcc $(OBJFILES) -o $(NAME)

$(OBJDIR)%.o: ./src/%.c
    mkdir -p $(OBJDIR)
    gcc -c $< -o $@

# If the make debug rule is called, I want my objects to be build into ./obj/debug/
debug: OBJDIR = ./obj/debug/
debug: all

With this Makefile, when I run make debug I have the following output:

❯ make debug
mkdir -p ./obj/debug/
gcc -c src/main.c -o obj/main.o
mkdir -p ./obj/debug/
gcc -c src/file.c -o obj/file.o
gcc ./obj/debug/main.o ./obj/debug/file.o -o myprog
gcc: error: ./obj/debug/main.o: No such file or directory
gcc: error: ./obj/debug/file.o: No such file or directory
gcc: fatal error: no input files
compilation terminated.
make: *** [Makefile:16: myprog] Error 1

My problem is into the two first rows of this output:

mkdir -p ./obj/debug/  # <----------- Here we see that the $(OBJDIR) variable has been updated

gcc -c src/main.c -o obj/main.o # <-- However, the $@ variable which is generated from $(OBJDIR)%.o hasn't been updated accordingly

How can I make the $@ automatic variable to update when $(OBJDIR) is changed ?

8
  • Your OBJFILES=$(addprefix $(OBJDIR), $(OBJNAMES)) appear to be set set before you update debug: OBJDIR = ./obj/debug/. You will need to make sure you have which directory to use set before you fill your ...FILES variables. Commented Oct 25, 2020 at 2:50
  • @DavidC.Rankin I don't think so, the OBJFILES variable is properly updated as you can see line 5: gcc ./obj/debug/main.o ./obj/debug/file.o -o myprog
    – avallete
    Commented Oct 25, 2020 at 3:02
  • Yes, but ./obj/debug/main.o ./obj/debug/file.o do not exist. They were compiled to cc -c src/main.c -o obj/main.o above. Commented Oct 25, 2020 at 3:02
  • @DavidC.Rankin Yes, that's my question. -o $@ should be ./obj/debug/main.o since it come from $(OBJDIR)%.o and OBJDIR is redefined by debug before the call to all. I can't really know in advance if the Makefile will be called with debug rule.
    – avallete
    Commented Oct 25, 2020 at 3:13
  • The only way I have handled this in the past is by passing a variable to make, e.g. make with=debug and then check ifeq ($(with), debug) then update the directory locations. Otherwise if make debug is supposed to handle that for you automatically -- I'd have to go back to the documentation and figure it out. Commented Oct 25, 2020 at 3:17

1 Answer 1

1

When make parses a Makefile it will expand all the variables upon building a dependency tree for targets that are not implicit targets. So when you call make debug it tries to build myprog, which in turn tries to build obj/main.o, not obj/debug/main.o, since the $(OBJFILES) has already been expanded when the rule for myprog was parsed. That's why $@ expands to obj/main.o even though the variable is correct. See for yourself:

$ make -dr debug
...
Considering target file 'debug'.
 File 'debug' does not exist.
 Looking for an implicit rule for 'debug'.
 No implicit rule found for 'debug'.
  Considering target file 'all'.
   File 'all' does not exist.
   Looking for an implicit rule for 'all'.
   No implicit rule found for 'all'.
    Considering target file 'myprog'.
     File 'myprog' does not exist.
      Considering target file 'obj/main.o'.
       File 'obj/main.o' does not exist.
       Looking for an implicit rule for 'obj/main.o'.
       Trying pattern rule with stem 'main'.
       Trying implicit prerequisite 'src/main.c'.
       Found an implicit rule for 'obj/main.o'.
        Considering target file 'src/main.c'.
         Looking for an implicit rule for 'src/main.c'.
         No implicit rule found for 'src/main.c'.
         Finished prerequisites of target file 'src/main.c'.
        No need to remake target 'src/main.c'.
       Finished prerequisites of target file 'obj/main.o'.
      Must remake target 'obj/main.o'.
mkdir -p ./obj/debug/
...

It is possible to make it work though. First of all, you would need to use second expansion to resolve dependencies at execution time. Second, you would need to make your $(NAME) target an implicit rule somehow to let make reevaluate the expression. Last, since your object location will differ (include debug or not), it will also propagate to the source location, which should generally be the same. I was able to make it work like this:

$ cat Makefile
SRCFILES=./src/main.c ./src/file.c
# Get the filenames of all the sources
SRCNAMES=$(notdir $(SRCFILES))

OBJDIR=./obj/
# Replace .c to .o in all source names
OBJNAMES=$(SRCNAMES:.c=.o)
# Add the obj/ repository as prefix
OBJFILES=$(addprefix $(OBJDIR), $(OBJNAMES))

NAME=myprog

.SECONDEXPANSION:

all: $(CURDIR)/$(NAME)

# Implicit rule due to %
%/$(NAME): $$(OBJFILES)
        gcc $(OBJFILES) -o $(NAME)

%.o: ./src/$$(notdir $$*).c
        mkdir -p $(OBJDIR)
        gcc -c $< -o $@

# If the make debug rule is called, I want my objects to be build into ./obj/debug/
debug: OBJDIR = ./obj/debug/
debug: all

Output:

$ make
mkdir -p ./obj/
gcc -c src/main.c -o obj/main.o
mkdir -p ./obj/
gcc -c src/file.c -o obj/file.o
gcc ./obj/main.o ./obj/file.o -o myprog
rm obj/main.o obj/file.o

$ rm -rf obj myprog
$ make debug
mkdir -p ./obj/debug/
gcc -c src/main.c -o obj/debug/main.o
mkdir -p ./obj/debug/
gcc -c src/file.c -o obj/debug/file.o
gcc ./obj/debug/main.o ./obj/debug/file.o -o myprog
rm obj/debug/main.o obj/debug/file.o

Personally I would also consider building final binaries with different name or location, otherwise you will never be sure what has been built.

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