views:

1867

answers:

1

I am using cmake to build my project. For UNIX, I would like to type make from my project's root directory, and have cmake invoked to create the proper Makefiles (if they don't exist yet) and then build my project. I would like the cmake "internal" files (object files, cmake internal Makefiles, etc.) to be hidden (e.g. put in a .build directory) so it doesn't clutter my project directory.

My project has several sub-projects (in particular, a library, a user executable, and a unit test executable). I would like Makefiles (i.e. I type make and this happens) for each sub-project to execute cmake (as above) and build only that sub-project (with dependencies, so the library would be built from the executables' Makefiles, if needed). The resulting binary (.so library or executable) should be in the sub-project's directory.

I made a Makefile which does the main project bit somewhat well, though it feels somewhat hackish. I can't build specific targets using it, because my Makefile simply calls make in cmake's build directory.

Note that because the library is a sole dependency (and probably doesn't need to be build manually, and because I'm lazy) I omitted it in my Makefile.

BUILD_DIR   :=  .build

.PHONY: all clean project-gui ${BUILD_DIR}/Makefile

all:    project-gui project-test

clean:
    @([ -d ${BUILD_DIR} ] && make -C ${BUILD_DIR} clean && rm -r ${BUILD_DIR}) || echo Nothing to clean

project-gui:  ${BUILD_DIR}/Makefile
    @make -C ${BUILD_DIR} project-gui
    @cp ${BUILD_DIR}/project-gui/project-gui $@

project-test:  ${BUILD_DIR}/Makefile
    @make -C ${BUILD_DIR} project-test
    @cp ${BUILD_DIR}/project-test/project-test $@

${BUILD_DIR}/Makefile:
    @[ -d ${BUILD_DIR} ] || mkdir -p ${BUILD_DIR}
    @[ -f ${BUILD_DIR}/Makefile ] || (cd ${BUILD_DIR} && cmake ${CMAKE_OPTS} ..)

If it helps, here's my project structure (if this is "wrong" please tell me -- I'm still learning cmake):

project/
project/CMakeLists.txt
project/common.cmake
project/Makefile -- see Makefile above for this; should be replaced with something better, building libproject, project-gui, and project-test

project/libproject/
project/libproject/CMakeLists.txt
project/libproject/libproject.so -- after build
project/libproject/Makefile -- doesn't exist yet; should build libproject only
project/libproject/source/
project/libproject/include/

project/project-gui/
project/project-gui/CMakeLists.txt
project/project-gui/Makefile -- doesn't exist yet; should build libproject then project-gui
project/project-gui/source/
project/project-gui/include/

project/project-test/
project/project-test/CMakeLists.txt
project/project-test/Makefile -- doesn't exist yet; should build libproject then project-test
project/project-test/source/
project/project-test/include/

If you haven't caught on yet, I'm basically looking for a way to build the project and sub-projects as if cmake wasn't there: as if my project consisted of only Makefiles. Can this be done? Is the solution elegant, or messy? Should I be trying to do something else instead?

Thanks!

+3  A: 

If cmake is generating the makefiles, you can simply include the generated makefile in the master makefile, eg

# makefile
all:                # Default
include $GENERATED
$GENERATED:$CMAKEFILE
   #  Generate the makefile here`

The included files are generated then make is restarted with the new included files. The included files should detail the targets, etc.

You should be able to change the location of used files using the vpath directive, see e.g. the Gnu make manual,

vpath %.o project/.build

else the tedious way is to rewrite the rules making note of the necessary directory.

Ed: Perhaps we shouldn't use a flat makefile.

Try something like:

# makefile
all: gui test
clean:
    $(MAKE) -f $(GUI-MAKE) clean
    $(MAKE) -f $(TEST-MAKE) clean

gui:$(GUI-MAKE)
    $(MAKE) -f $(GUI-MAKE) all
$(GUI-MAKE):$(GUI-CMAKE)
    # Generate

# Same for test

This should work if the $(MAKE) -f $(GUI-MAKE) all command works on the command line, and we've hidden cmake in the generating target. You would have to copy any other targets to the master makefile as well, and take care running make in parallel.

Propagating object files through should involve something like

%.o:$(GUI-MAKE)
    $(MAKE) -f $(GUI-MAKE) $@

although you'll probably get errors trying to make test objects

cramsin
Wow! I didn't even think of that! There is one problem though: the Makefile included ($GENERATED) expects a different pwd/$CUR_DIR than the project dir (it thinks it's in project/.build). Any way to fix this?
strager
I don't think you can change the working directory between rules, however I've added a note on vpath, which should allow you to find your object files etc in the correct place.
cramsin
I get the following error, without vpath: make[1]: CMakeFiles/Makefile2: No such file or directory. It is caused by the cmake-generated Makefile calling: $(MAKE) -f CMakeFiles/Makefile2 all. CMakeFiles is a sub-directory in my build directory. [continued ...]
strager
[...] I tried using vpath in the following ways: VPATH=${BUILD_DIR} at the top of my Makefile, as well as export VPATH=${BUILD_DIR}. Neither worked (same error). Then I tried: vpath CMakeFiles/% ${BUILD_DIR}. Again, no change. Am I using the directive improperly? Shall I paste cmake's Makefile?
strager
Sure. I assume cmake's makefile works fine from it's own directory, and that running make -f CMakeFiles/Makefile2 from the master makefile's directory gives the same error?
cramsin
@cramsin, That is correct. Running from pwd=${BUILD_DIR} runs fine, as expected. This is what my old Makefile did.
strager
Your update is pretty much what I have now. I would like to be able to build specific targets e.g. specific .o files, if possible. The include solution seems close. If only there's a way to temporary change the current working directory...
strager
Each 'command' in the makefile is run in a separate shell, so all you need to do is give one command that changes directories and then runs something. target: deps (cd $DIR; $(MAKE) target)You may also want to look at the -C flag to Make
Novelocrat