Search code examples
c++makefileprerequisites

makefile with conditionally generated file


How do I convey to make that a script can conditionally modify a file, so that make waits to stat all the files until that script finishes running?


Consider the following toy example series of files:

// version
2
// foo.h - note the space between the 1 and the semicolon
static constexpr int VERSION = 1 ;
//update.py
import sys 

with open(sys.argv[1]) as f:
    cur = int(f.readlines()[0].split()[5])

with open('version') as f:
    exp = int(f.readlines()[0].strip())

if cur < exp:
    with open(sys.argv[1], 'w') as f:
        print >> f, 'static constexpr int VERSION = {} ;'.format(exp)
// main.cxx
#include "foo.h"
#include <iostream>

int main() {
    std::cout << VERSION << std::endl;
}
// makefile
all : a.out updater

a.out : main.o
    g++ -o a.out main.o

main.o : main.cxx
    g++ -std=c++11 -o main.o -c main.cxx -MP -MMD -MF main.d

.PHONY : updater
updater :
    python update.py foo.h

-include main.d

The intent here is that I have a file, foo.h, which is conditionally updated by a script, update.py (if you increase the number in version, foo.h will be updated - otherwise nothing will happen). I would like to convey this notion to make somehow - that it is not to stat foo.h in order to determine if it needs to remake main.o until update.py finishes running. How do I ensure that ordering?

Note: version here is simply a placeholder for a complex series of prerequisites that are not easy to express. Simply adding foo.h : version is not a solution.


Solution

  • First, if you don't want make to update main.o etc. until after foo.h has been updated you have to list foo.h as a prerequisite:

    main.o : main.cxx foo.h
            g++ -std=c++11 -o main.o -c main.cxx -MP -MMD -MF main.d
    

    (Although of course I would never write it like this, I would always use variables like this:

    CXX = g++
    CXXFLAGS = -std=c++11
    DEPFLAGS = -MP -MMD -MF $*.d
    
    main.o : main.cxx foo.h
            $(CXX) $(CXXFLAGS) $(CPPFLAGS) $(DEPFLAGS) -o $@ -c $<
    

    but that's me...)

    Second, you have to have a rule to update foo.h. As you suggest, it should be .PHONY so it's always run, even though it doesn't always update foo.h:

    .PHONY: updater
    updater:
            python update.py foo.h
    

    Now you have to connect these two rules, so you have to create a rule that links foo.h to updater:

    foo.h : updater ;
    

    Note the semicolon here, that's critical. The way it works is this: updater is phony so it will always run, and that means that any target the depends on it will always run. So clearly you can't list main.o : main.cxx updater as that would rebuild main.o every time.

    So you can introduce an intermediate rule, foo.h : updater. You need to do this anyway since you need to list foo.h as a prerequisite so you need a target to build it.

    However, by itself this won't help because make sees that there's no recipe to build foo.h, so it doesn't realize that when updater was run it built foo.h. You get around this by creating an empty recipe for foo.h; that's what the ; is for:

    foo.h : updater ;
    

    that creates an empty recipe for this target foo.h and that's enough to get make to check to see whether the file foo.h has been modified or not. When it is modified (by updater) then main.o will be rebuilt. When it is not modified, main.o will not be rebuilt (unless there's some other reason to rebuild it).