Q:

Is there a **simple** way to redirect object file creation into a specified directory with Makefile project?

The project I work on is organised in one folder with a root Makefile, and a lot of sub-project each one being a subfolder containing its own makefile. 
The root Makefile invokes make into each subdirectory (make -C command). Object files are generated at the same level as the source files.
I would like to order the root make command to redirect object file generation(and retrieving) into a specified build_dir. Is there a simple way of doing this ? (Instead of modifiying every Makefiles in every sub-project).

A:

It's kind of a hack, but you can do this with a combination of a compiler wrapper and vpath.
Suppose we have foo.c:
#include <stdio.h>

int main() {
    printf("hi there\n");
    return 0;
}

and bar.c:
int bar() {
    return 1;
}

Putting vpath %.o obj inside the Makefile will tell make to look inside the obj/ directory for object files. But we need to tell the compiler to write object files into the obj/ directory. gcc has no such option—but we don’t have to use gcc, we can write our own compiler wrapper that calls gcc with a modified command line.
cc-wrapper:
#!/bin/bash

OUTDIR=obj

#

function push() {
  # usage: push ARRAYNAME ARG
  # adds ARG to the end of ARRAY
  eval $1[\${#$1[@]}]="\$2"
}

ARGS=()
change_dir=false

mkdir -p "${OUTDIR}"

for (( i = 1; i <= $#; i++)); do
      eval arg="\${$i}"
      if $change_dir; then
        arg="${OUTDIR}/${arg}"
        change_dir=false
      fi
      if [ -e "${OUTDIR}/${arg}" ]; then
          arg="${OUTDIR}/${arg}"
      fi
      if [ "${arg}" = "-o" ]; then
        change_dir=true
      fi
      push ARGS "${arg}"
done

echo gcc "${ARGS[@]}"
exec gcc "${ARGS[@]}"

It’s an ugly shell script, but all it's doing is modifying some arguments:

if the previous argument was -o, add obj/ to the start of this one
if the current argument is a file in obj/, add obj/ to the start

and then calling gcc.
Then the Makefile is:
CC=./cc-wrapper

vpath foo obj
vpath %.o obj

foo: foo.o bar.o

clean::
    rm -rf foo.o bar.o foo obj

Now, all your objects go in obj/ and make tracks dependencies correctly.
It'll take some tuning to make this ready for production—you'll probably want to rewrite the script in a comprehensible language like Python—but this should help you get started.

