1

I have a makefile that for various reasons relies on a supporting python script to run every time and grab files from several external locations, copy into working directory, and run through a separate preprocessor before compiling.

This makefile must be able to be run in parallel (-j8) so the order of processing cannot be guaranteed.

In trying to explicitly specify prerequisites, I have created a situation where make skips all object files, goes straight to linking, and fails because the necessary objects do not exist. On a second run, all the objects already exist (the preprocess step skips the files that already exist) and all the files are compiled and linked properly.

When run without -j# everything works fine, but the moment I add -j2, the skipping begins.

Following is an example make file:

GEN_FILES := file1.cpp file2.cpp file3.cpp
CXX_FILES := bin_main.cpp $(GEN_FILES)
OBJ_FILES := $(patsubst %.cpp,%.o,$(CXX_FILES))

.PHONY : all clean prepare
all : bin_file

prepare :
# Copy and preprocess all source files
    [ -f file1.cpp ] || cp d1/file1.cpp .
    [ -f file2.cpp ] || cp d2/file2.cpp .
    [ -f file3.cpp ] || cp d3/file3.cpp .

$(OBJ_FILES) : prepare

bin_file : $(OBJ_FILES)
    [ -f file1.o ] && [ -f file2.o ] && [ -f file3.o ] && touch bin_file

%.o : %.cpp
    @echo "Compiling $<..."
    [ -f $< ] && touch $@

clean :
    $(RM) *.o
    $(RM) file*
    $(RM) bin_file

How can I get this to build in one go, first running prepare to collect all files and then compiling and linking as necessary?

Squaven
  • 13
  • 3
  • 1
    This paraphrasing is hiding the problem with your makefile. There's no way the makefile you show here can behave the way you say it will. In fact, not only will it wait for the object files to be built, it will always rebuild all the object files every time even if you don't change anything, because all the object files depend on the `prepare` target, which is `.PHONY`. And even if it weren't `.PHONY`, the `prepare` rule will never create the `prepare` target so it will never be up to date. – MadScientist Feb 06 '21 at 00:01
  • 2
    Please create a minimal complete example that, when you run it, shows the problem, then edit your question to include that. It's easiest if your recipes don't actually run a compiler or run any internal command like `copy_and_pp_files.py` since we don't know what that does. Simply use `cp`, `touch`, etc. to simulate the files that are created and updated. – MadScientist Feb 06 '21 at 00:03
  • First, I've edited to remove any non-standard operations. This should run and repeat the problem. Second, that is the problem in a nutshell. How do I run the copy operation first every time then allow make to properly only build the files that actually needed to be copied in the first operation? – Squaven Feb 07 '21 at 13:41
  • @Squaven I just added a note into the answer about how you are coping, to try to avoid *always* re-compiling your copied files... – code_fodder Feb 07 '21 at 15:55

2 Answers2

1

As code_fodder mentions the issue is the creation of the source files.

Basically what happens is, you have not told make how to create these source files, so as far as make knows they don't exist and there's no way to create them. So when make wants to build, for example, file1.o it looks at your pattern rule and finds it could build file1.o from file1.cpp. So then it looks for how to build file1.cpp. No file1.cpp exists, and there is no rule that make knows of that will build it, so make ignores that pattern rule as not matching.

Then make sees the target:

$(OBJ_FILES) : prepare

so it thinks there's no recipe needed to create the object files, and just runs the link line. The next time through, make sees the prepared source files (from the previous build) and then it can use your pattern rule.

If you change your pattern rule to a static pattern rule, where you explicitly tell make exactly what rule to use instead of providing it with a possible rule to use that it can ignore if it doesn't match (which is what a pattern rule is), you'll see the error:

$(OBJ_FILES): %.o : %.cpp
        @echo "Compiling $<..."
        sleep 1
        [ -f $< ] && touch $@

will tell you:

make: *** No rule to make target 'file1.cpp', needed by 'file1.o'.  Stop.

Remember, make is looking for a matching pattern rule BEFORE it actually builds anything: it doesn't want to build every possible prerequisite of every possible matching pattern rule, to decide whether or not at the end of it the rule can be used. The rule is matched based on the current state of the filesystem plus rules you have given make about changes it could make. Make has no idea that if it were to invoke the prepare target the source files it was looking for would magically come into existence.

Your basic problem is that this statement is the wrong dependency relationship:

$(OBJ_FILES) : prepare

It's not really true that the object files depend on prepare; what's true is that the PREPARED SOURCE FILES depend on prepare. The object files depend only the "prepared" source files, as your pattern rules shows. This rule should be written, instead:

$(GEN_FILES): prepare

If you do this with -j everything will wait as you want.

MadScientist
  • 92,819
  • 9
  • 109
  • 136
  • I have not tried this, but this looks good - my answer is a bit more of a brute force and generic way to force some order. But this looks more correct for this use-case, nice – code_fodder Feb 08 '21 at 20:57
0

Yeah, this gets messy / difficult. The problem you have is that you can specify prerequisite lists - that can work in order, but as soon as you start to use -j then make can start processing prerequisites in any old order. So bin_file requires $(OBJ_FILES) which require prepare. Then %.o requires the same named %.cpp file - which it can do for main.o, but not the filex.o since they don't exist yet - but it tries anyway and fails - in the mean time make (in parallel) is potentially starting to generate the .cpp files, but by this time its too late...etc...

My Prerequisites Build Pattern

I use a very specific prerequisites pattern of my own design - some might frown upon - but I have carefully considered this over the years and found it to be optimal for me.

I create a rule called build or something - which requires build_prerequisites target and then calls make to do the actual build once this is complete:

.PHONY: build
build: build_prerequisites
build:
    @echo "start_build"
    @$(MAKE) bin_file

This means that build_prerequisites is always run first before the recipe runs. You cant seem to achieve the same forcing of order (at least not easily) using just dependencies. I.e. a list of dependencies can be run in any order with -j, but the rule recipe is always run last.

Now we have this pattern we can fill in the rest. First the build_prerequisites target which does your file generation - I am using echo in my example because I don't have your python script:

.PHONY: build_prerequisites
build_prerequisites:
    @echo "build_prerequisites"
    echo "create file1" > file1.cpp
    echo "create file2" > file2.cpp
    echo "create file3" > file3.cpp

Finally add in the c++ compile and link stages - these will be run with the single recursive make call from build - i.e. $(MAKE) bin_file (again I am using echo to create the files in my example):

%.o : %.cpp
    @echo "compiling: $<"
    @#echo "$(CXX) $(SRC_INCLUDES) $(LIB_INCLUDES) $(CXXFLAGS) -c $< -o $@"
    @echo "touch" > $@

bin_file : $(OBJ_FILES) 
    @echo "linking: $<"
    @echo $(CXX) $(SRC_INCLUDES) $^ $(LIB_INCLUDES) $(LDFLAGS) -o $@
    @echo "touch" > $@

Output

Here is the output from my test program (using echo) and main.cpp already exists usingn -j10:

make -j10
build_prerequisites
echo "create file1" > file1.cpp
echo "create file2" > file2.cpp
echo "create file3" > file3.cpp
start_build
make[1]: Entering directory '/mnt/d/software/ubuntu/make'
compile: bin_main.cpp
compile: file1.cpp
compile: file2.cpp
compile: file3.cpp
link: bin_main.o
g++ bin_main.o file1.o file2.o file3.o -o bin_file
make[1]: Leaving directory '/mnt/d/software/ubuntu/make'

Note: if I put a sleep 1 in the "compile" rule - this still takes only 1 second for all 4 files to compile.

Put it all together

GEN_FILES := file1.cpp file2.cpp file3.cpp
CXX_FILES := bin_main.cpp $(GEN_FILES)
OBJ_FILES := $(patsubst %.cpp,%.o,$(CXX_FILES))

###### STAGE 1

.PHONY: build
build: build_prerequisites
build:
    @echo "start_build"
    @$(MAKE) bin_file

.PHONY: build_prerequisites
build_prerequisites:
    @echo "build_prerequisites"
    copy_and_pp_files.py $(CXX_FILES) $(SEARCH_DIRS) .
    copy_and_pp_files.py $(CFG_FILES) $(SEARCH_DIRS) .

###### STAGE 2

%.o : %.cpp
    @echo "compiling: $<"
    @$(CXX) $(SRC_INCLUDES) $(LIB_INCLUDES) $(CXXFLAGS) -c $< -o $@

bin_file : $(OBJ_FILES) 
    @echo "linking: $<"
    @$(CXX) $(SRC_INCLUDES) $^ $(LIB_INCLUDES) $(LDFLAGS) -o $@

###### OTHER RULES

.PHONY: clean
clean :
    @$(RM) *.o
    @$(RM) file*

I have attempted to use your actual code, but I have no way to test this so there may be a bug in there. I split it up into 2 "stages" for clarity. Stage 1 is done in your makeor make build call, then state 2 is done in the recursive make call in the build recipe.

code_fodder
  • 15,263
  • 17
  • 90
  • 167
  • This works great! I was considering just splitting all the prerequisites into a separate make session through recursive make but this is cleaner than the way I was going. Thanks for the help! – Squaven Feb 07 '21 at 13:50
  • Just note: now that I have seen how you are copying - you might consider using something like rsync to do the copying. The issue is every time you copy the files make things the filex.cpp have been updated and will need re-compiling. `rsync` does a similar check to make using timestamps. So if the file to be copied has not changed, then rsync does not copy it and make will not re-compile it. This should save you having to *always* recompile the copied files. You can more or less replace `cp` with `rsync` – code_fodder Feb 07 '21 at 15:53