Updating a legacy C makefile to include a C++ source file
I work in a computational biophysics lab. I am not a programmer, although I do get paid to act like one. Here's my problem: the main product of the lab is a ginormous (50+ source files) C program. I need to get our lab's program to work with another lab's toolkit, which just so happens to be in the form of a series of C++ libraries (.a files). I can get the main library for our program to compile using the following makefile:
CC = gcc
#CC = icc
CFLAGS = -g -Wall
#CFLAGS = -xT -openmp -I/opt/local/include -I/usr/local/include -I/opt/GDBM/include
#CFLAGS = -O3 -g -Wall -I/opt/GDBM/include -fopenmp
LIB = mcce.a
AR = ar
ARFLAGS = rvs
SRC = all.c ddvv.c geom_3v_onto_3v.c ins_res.c strip.c\
app.c del_conf.c geom_apply.c line_2v.c vdotv.c\
avv.c del_prot.c geom_inverse.c load_all_param.c vector_normalize.c\
avvvv.c del_res.c geom_move.c load_param.c vector_vminusv.c\
cpy_conf.c det3.c geom_reset.c mxm4.c vector_vplusv.c\
cpy_prot.c det4.c geom_roll.c new_prot.c vector_vxv.c\
cpy_res.c dll.c get_files.c param_get.c param_exist.c\
db_close.c dvv.c iatom.c param_sav.c\
db_open.c free_strings.c ins_conf.c plane_3v.c pdbline2atom.c\
premcce.c init.c load_pdb.c write_pdb.c rotamers.c assign_rad.c get_connect12.c\
surfw.c vdw.c vdw_conf.c shuffle_n.c cmp_conf.c sort_conf.c sort_res.c id_conf.c\
energies.c assign_crg.c coulomb.c coulomb_conf.c\
get_vdw0.c get_vdw1.c relax_water.c relax_h.c monte.c monte2.c ran2.c\
relaxation.c collect_connect.c torsion.c vdw_fast.c hbond_extra.c swap.c quick_e.c\
check_tpl.c zip.c del_dir.c make_matrices.c\
mem_position.c probe.c add_membrane.c load_pdb_no_param.c ga_engine.c rotamers_ga.c compute_patches.c
OBJ = $(SRC:.c=.o)
HEADER = mcce.h
$(LIB): $(OBJ)
$(AR) $(ARFLAGS) $(LIB) $(OBJ)
$(OBJ): $(HEADER)
.c.o:
$(CC) $(CFLAGS) -c $*.c
clean:
rm -f *.o mcce.a
The executable itself then compiles with this makefile:
CC = gcc -g -O3
#CC = icc -xT -static-intel -L/opt/local/lib -L/usr/local/lib
mcce: mcce.c lib/mcce.h lib/mcce.a
# $(CC) -o mcce mcce.c mcce.a /opt/GDBM/lib/libgdbm.a -lm -lz -openmp; cp mcce bin
$(CC) -o mcce mcce.c lib/mcce.a -lgdbm -lm -lz -fopenmp; cp mcce bin
I can get a standalone version of the other lab's code to compile using this other makefile:
OEDIR = ../..
INCDIR = $(OEDIR)/include
LIBDIR = $(OEDIR)/lib
INCS = -I$(INCDIR)
LIBS = -L$(LIBDIR) \
-loezap \
-loegrid \
-loefizzchem \
-loechem \
-loesystem \
-loeplatform \
-lz \
-lpthread -lm
CXX = /usr/bin/c++
RM = rm -f
CXXFLAGS = -m64 -W -Wall -O3 -fomit-frame-pointer -ffast-math
LFLAGS = -m64 -s
TEXT2HEX = ../text2hex
PROGRAMS = other_labs_code
.SUFFIXES: .cpp
.SUFFIXES: .o
.cpp.o:
$(CXX) $(CXXFLAGS) $(INCS) -c $<
.SUFFIXES: .txt
.SUFFIXES: .itf
.txt.itf:
$(TEXT2HEX) $< InterfaceData > $@
all: $(PROGRAMS)
clean:
$(RM) $(PROGRAMS)
$(RM) ii_files core a.out *.itf
$(RM) *.o
other_labs_code.o: other_labs_code.cpp other_labs_code.itf
other_labs_code: other_labs_code.o
$(CXX) other_labs_code.o $(LFLAGS) -o $@ $(LIBS)
I know that I have to change the paths of the various libs and stuff, but other than that, how do I combine all of these makefiles into one working product? Also, since some of the source files that go into compiling my program's main library (mcce.a) are going to need to be able to call functions from the C++ source file, it's the library's makefile that I need to modify, right?
I know extremely little about makefiles, so even if someone can just point me in the direction of a tutorial that covers this kind of problem (writing a makefile for a many source file C and C++ program), that may be sufficient.
For bonus points, the C++ FAQ says that:
You must use you开发者_开发问答r C++ compiler when compiling main() (e.g., for static initialization)
Your C++ compiler should direct the linking process (e.g., so it can get its special libraries)
I don't exactly know what those things are supposed to mean, but assuming that I did, are there any other important points like that I should be aware about when combining C and C++?
Preparing the code
C programs cannot just use C++ symbols. Unless the authors of the C++ code arranged for that. This is because some features that C++ offers, such as function overloading (having several functions of the same name but with different formal arguments) demand that the function name be mangled in some way. Else the linker would see the same symbol defined several times. C compilers don't understand this name mangling and therefore cannot use C++ symbols. There are, generally, two possible solutions.
- Declare and define all C++ symbols that the C code wants to use within
extern "C" { ... }
blocks and let your C++ tools handle the linking. The C code does not need to be changed in this case. - Compile the C code with the (exact same) C++ compiler as the C++ code. Fix the C++ compiler's complaints of the C code as they arise. Depending on project size and coding style, this may or may not be a lot of work.
Preparing a master Makefile
I personally try to avoid becoming intimate with other people's Makefiles, especially if they are subject to change or complex. So, assuming generating a Makefile that orchestrates the bits you already have (as opposed to writing one Makefile incorporating everything) is okay, I'd start out with something similar to this:
I'm assuming that
- One of the above-mentioned options has been implemented
- The code for
mcce.a
lies in subdirectorymcce/lib/
other_labs_code.cpp
lies inother_labs_code/
- The
main
function you want to use lies in./mystuff.c
the following top-level Makefile may get you started
CXX = c++
CXXFLAGS = -m64 # From other_labs_code/Makefile
LDFLAGS = -m64 -L<path to OEDIR> # From other_labs_code/Makefile
LIBS = -lgdbm -lm -lz # From mcce/lib/Makefile
LIBS += -loezap \ # From other_labs_code/Makefile
-loegrid \
-loefizzchem \
-loechem \
-loesystem \
-loeplatform \
-lpthread
mystuff: mystuff.c mcce/lib/mcce.a other_labs_code/other_labs_code.o
$(CXX) $(CPPFLAGS) $(CXXFLAGS) $(LDFLAGS) -o $@ $^ $(LIBS)
mcce/lib/mcce.a:
cd mcce/lib/ && $(MAKE) CC="$(CXX) -m64" mcce.a
other_labs_code/other_labs_code.o:
cd other_labs_code/ && $(MAKE) other_labs_code.o
Makefile: mcce/lib/Makefile other_labs_code/Makefile
echo "Warning: `pwd`/$@ is out of date" >&2
This Makefile will employ the existing sub-project Makefiles to do the compilations. If the sub-project Makefiles have a newer timestamp than this Makefile, potentially rendering it obsolete, then this will be warned about. The linking basically works by combining the required libraries of both sub-projects. I've removed duplicates. The compiler switches are basially those of the original authors since compiling is delegated to the sub-projects. The code both sub-projects generate must be for the same platform. If your compiler is gcc/g++ then either -m64
is the default and therefore redundant in the second project or should be added to the first project. I have illustrated injecting it into the first project without changing their Makefile (using GNU make). NB: This example also causes the first project to be compiled with the C++ compiler.
An extern "C" {...}
block located in a C or C++ header file that C code wants to include should look like this
/* inclusion guard etc */
#if defined(__cplusplus)
extern "C" {
#endif
/* C declarations */
#if defined(__cplusplus)
}
#endif
/* inclusion guard etc */
Minor points
In the first posted Makefile, I suggest changing the bottom part to
.c.o:
$(CC) $(CFLAGS) -c -o $@ $<
clean:
rm -f $(OBJ) mcce.a
.PHONY: clean
which is a tiny bit cleaner.
The second Makefile is broken. The bottom rule links the binary and then copies it to a directory named bin, if it exists, else a copy of the file is created and named `bin'. If the linking fails, that fact is not propagated to the caller, i.e. the error is ignored. The bottom rule should read
mcce: mcce.c lib/mcce.h lib/mcce.a
$(CC) -o $@ mcce.c lib/mcce.a -lgdbm -lm -lz -fopenmp
cp mcce bin/
i.e. the link command should be on its own line and that `bin' is supposed to be a directory should be made explicit.
http://www.gnu.org/software/make/manual/make.html#Introduction
精彩评论