2
votes

I need some help writing a GNU makefile. I have a C program "main.c", which is dependent on the value of "CONSTANT" defined in the file "constants.h".

"main.c"

#include <stdio.h>
#include "constants.h"

void work(void)
{
    int array[CONSTANT];
    for (int i = 0; i < CONSTANT; i++) {
        printf("%d\n", i);
    }
}

int main(int argc, char const* argv[])
{
    printf("constant=%d\n", CONSTANT);
    work();
    return 0;
}

"constant.h"

#ifndef CONSTANTS_H
#define CONSTANTS_H

#define CONSTANT 4
#endif

What I'm trying to do here is to compile the program with different values for "CONSTANT". For example, "out1" is compiled with "CONSTANT=1" and with "make all", I should be able to produce all the variants ("out1", "out2" and "out4").

The problem is that "a.o" required by "main.c" also depends on the value of "CONSTANT". So "a.o" must be compiled after "sed%". However, as far as I understand, there is no way in "make" to force orders in dependencies (I guess this is the whole point of using makefiles).

What is the recommended way to address this situation?

"Makefile"

CC= gcc
CFLAGS  = -std=c99 -Wall

CONSTANTS = 1 2 4
targets = $(addprefix out, $(CONSTANTS))
seds = $(addprefix sed, $(CONSTANTS))

.PHONY: $(seds)
$(seds): sed%:
  sed -i 's/define CONSTANT [0-9]*/define CONSTANT $*/g' constants.h

$(targets): out%: main.c sed% a.o
  $(CC) $(CFLAGS) $< a.o -o $@

a.o: a.c constant.h
  $(CC) $(CFLAGS) $< a.o -o $@

 .PHONY: all
 all : $(targets)

Note that I'm aware that I can rewrite "main.c" so that it takes a parameter from the comman line. In practice, many other files other than "main.c" depend on "CONSTANT", so I want to avoid rewriting all these files. I'm also aware that I can do something like "gcc -DCONSTANT=n main.c", but every file dependent on "CONSTANT" must be recompiled as well.

Related Questions

1
Make assumes that once it has built a file (a.o), it is up to date and won't need re-recompiling during the same run. Therefore, you have to do something else. One option would be to create a1.o, a2.o and a4.o from a.c and constants.h and suitable sed-work. If there's only one constant, consider -DCONSTANT=1 on the command line as you create a1.o from a.c, etc, without using constants.h at all. However, I suspect that this is a matter of minimization for SO, and you have more values than just the one to set. I still tend towards creating differently named object files. - Jonathan Leffler
Please provide a minimal reproducible example with a test. I.e. define a situation (constant.h) changed or a certain way of calling the makefile) and a desired result, i.e. an output or a resulting content of files. - Yunnosch
@JonathanLeffler I went for generating separate object files (a1.o, a2.o, ....) as you and Mike suggested. My program has only one constant to configure, so your solution worked for me. Thank you. - user3127171
Welcome to Stack Overflow. Please note that the preferred way of saying 'thanks' around here is by up-voting good questions and helpful answers (once you have enough reputation to do so), and by accepting the most helpful answer to any question you ask (which also gives you a small boost to your reputation). Please see the About page and also How do I ask questions here? and What do I do when someone answers my question? - Jonathan Leffler

1 Answers

3
votes

I'm ... aware that I can do something like "gcc -DCONSTANT=n main.c", but every file dependent on "CONSTANT" must be recompiled as well.

This needn't be a hindrance if you have your makefile generate the correct -DCONSTANT=n and distinct object file in every compilation recipe.

Here's an illustration:

constants.h

#ifndef CONSTANTS_H
#define CONSTANTS_H

#ifndef CONSTANT
#define CONSTANT 4
#endif

#endif

foo.c

#include "constants.h"

int foo = CONSTANT;

main.c

#include <stdio.h>
#include "constants.h"

extern int foo;

int main()
{
    printf("%d\n",CONSTANT + foo);
    return 0;
}

Makefile

CC := gcc
CFLAGS := -std=c99 -Wall

CONSTANTS = 1 2 4
TARGETS = $(addprefix out, $(CONSTANTS))
SRCS := main.c foo.c

define compile =
$(basename $(1))$(2).o: $(1) constants.h
    $$(CC) -c -DCONSTANT=$(2) $$(CFLAGS) $$< -o $$@
endef

.PHONY: all clean

all : $(TARGETS)

$(foreach src,$(SRCS),\
    $(foreach const,$(CONSTANTS),$(eval $(call compile,$(src),$(const))))) 

out%: main%.o foo%.o 
    $(CC) $^ -o $@


clean:
    rm -f $(TARGETS) *.o

This runs like:

$ make
gcc -c -DCONSTANT=1 -std=c99 -Wall main.c -o main1.o
gcc -c -DCONSTANT=1 -std=c99 -Wall foo.c -o foo1.o
gcc main1.o foo1.o -o out1
gcc -c -DCONSTANT=2 -std=c99 -Wall main.c -o main2.o
gcc -c -DCONSTANT=2 -std=c99 -Wall foo.c -o foo2.o
gcc main2.o foo2.o -o out2
gcc -c -DCONSTANT=4 -std=c99 -Wall main.c -o main4.o
gcc -c -DCONSTANT=4 -std=c99 -Wall foo.c -o foo4.o
gcc main4.o foo4.o -o out4

And the resulting programs run like:

$ for i in 1 2 4; do ./out$i; done
2
4
8