1

Suppose I have a C++ source file which looks like:

int main() {
...
#ifdef flag
    // do something
#endif
...
#ifndef flag
    // do something different
#endif
...
}

I know that for gcc I can use -D'flag' as a directive to the pre-processor to define flag and thus determine which part of the code above actually gets compiled, and that this can be used in a Makefile.

But how do I write a Makefile such that the source file gets re-built based on whether I want flag defined or not (with no other changes to any file, including the Makefile itself)?

I know that I can, for example (and the solution doesn't need to follow this if there is a better way!), define a variable when I run make e.g. with make flag=1 all, but this will not trigger a rebuild if the file containing main (in line with the source code example above) has not been changed since.

i.e. starting from a clean directory and running make all will run the build, but then if immediately I run make flag=1 all I would like the file containing main (say main.cpp) to be rebuilt correctly without having to touch main.cpp first.

ogb119
  • 119
  • 1
  • 10
  • I would suggest using cmake. It rebuilds the target if you run cmake with changed flags. – 273K Dec 11 '20 at 14:58
  • The "memory" of whether the last build used the flag must be stored somewhere. Make can write a file to record that information. Is that approach acceptable? – Beta Dec 11 '20 at 15:19
  • @Beta would be interested to see how that works, thanks – ogb119 Dec 11 '20 at 15:35

2 Answers2

5

First of all make must know that the flag has changed. That means you must store the flag "somewhere" which is typically some file. The next you have to tell make which file depends on your flag. It is not necessary to compile all and everything but the dependent files. This means, you have to specify which source depends on which flag, or in this case, on which file where the flag is stored.

OK, all together you Makefile can be something like that:

# define a first rule, that we want, if no target is given, we want to
# compile our program named "go"
all: go

# read the file, where our last given flag was stored.
# the minus in front tells make, that if the file can't be read, simple ignore it!
-include stored_flag.mk

# after that, we compare the old flag and the new one which was given on command
# line or from environment. If it differs or was not present before,
# write the new flag to the file to make it reusable for the next call
ifneq ($(OLDFLAG),$(FLAG))
$(file > stored_flag.mk, OLDFLAG:=$(FLAG))
endif

# now we forward the environment variable to the compiler
# if it is present
ifneq ($(FLAG),)
CXXFLAGS=-DFLAG=$(FLAG)
endif


# as said: we need to make the source files dependent on the flag file,
# if the flag is used in that source. If ot, no need to comoile even if flag has 
# changed
main.o: stored_flag.mk

# standard rule: 
main.o: main.cpp

# build our program "go" 
go: main.o 
    g++ $< -o go

Example source (main.cpp):

#include <iostream>

int main()
{
#ifdef FLAG
    std::cout << "Flag is " << FLAG << std::endl;
#else
    std::cout << "No flag" << std::endl;
#endif
}

Execute with:

make FLAG=1
make FLAG=1
make FLAG=2
...

Happy testing!

If you have multiple flags, and you want to define the dependencies independent for each flag, you can use multiple files to store the flags.

Add on:

Q:"is the only way Make knows to trigger a rebuild by comparing timestamps of dependencies vs the target?"

A: That is the core idea of make! Compare timestamps on files to determine the need of doing something. BUT: As you can see make can deal with "ifdef" and so you can define rules inside such a block which makes compilation depending on some conditions without comparing timestemps nor defining dependencies.

Klaus
  • 24,205
  • 7
  • 58
  • 113
  • This is very helpful, thank you. I suppose a more fundamental question would have been: "is the ***only*** way Make knows to trigger a rebuild by comparing timestamps of dependencies vs the target?" Seems like the answer is "yes", and in that case I now understand why you have to write the previous flag used to a file and include that file as a dependency! However, this answer doesn't fully solve my problem; I have to use `#if FLAG==1` and `#if FLAG==0` in the source code instead of `#ifdef FLAG` and `#ifndef FLAG`, and thus I can't just run `make` on it's own (i.e. to leave `FLAG` undefined) – ogb119 Dec 11 '20 at 23:03
  • @ogb119: Instead of "very helpful" we typically up-vote here... – Klaus Dec 12 '20 at 08:41
  • @ogb119 add an answer to your question – Klaus Dec 12 '20 at 08:45
  • @ogb119 And now also update for the use case "with changing or on flag provided on command line" – Klaus Dec 12 '20 at 08:50
  • The updated answer is exactly what I was looking for, thanks – ogb119 Dec 12 '20 at 10:13
  • similar way one can automatically rebuild file based on any dependencies or on header files. make doesn't do that either, but in case of GNU-related compiler can output a "dependency" file to be included into makefile which is essentially a target code for a .ccp file mentioning all headers. But timestamps isn't only thing can be used. – Swift - Friday Pie Dec 12 '20 at 11:27
0

You can create a dependency to the Makefile for your code. That will trigger a compile when the Makefile changes.

Or use cmake, then your IDE will/should do this for you...

U. W.
  • 414
  • 2
  • 10
  • I won't be making changes to the Makefile. I'd like the rebuild to be triggered based on what arguments I pass to `make` in the command line. I've edited my question to be more explicit about this. Thanks for the cmake tip, I am just learning make at the moment with the intention to learn cmake next! – ogb119 Dec 11 '20 at 15:14
  • Hmmm, then you might create a special target in your Makefile that uses "touch" to trigger certain actions. – U. W. Dec 15 '20 at 08:24