Vemulo Vemulo - 1 year ago 131
C Question

Converting makefile with circular link dependency to CMake

I have multiple implementations(.c or .cpp files) that implements different function of the same header file. I have the makefile approach for build and I want to reimplement it with cmake.(Note that I cannot change the implementation files or headers. If I could, I wouldn't design and implement the solution as it is.) I have prepared smaller case to point out my issue.


#ifndef __HEAD__
#define __HEAD__

int foo(float e, float b);
void boo(float *eo, float *eh);
void setup();
int load(int* n);



#include "head.h"

void boo(float *eo, float *eh)
*eo = 0.4f;
*eh = 2.3f;


#include <stdio.h>
#include "head.h"
void setup()
int m = 13;
int* n = &m;


#include "head.h"
int load(int* n)
n = 0;
return 0;


#include "head.h"
int main()
return 0;
int foo(float e, float b)
float i, j;
boo(&i, &j);
return 4;

makefile:(this is building and linking)

# C compiler
CC = g++
CC_FLAGS = -g -O2

main: impl1.o impl2.o impl3.o
$(CC) $(CC_FLAGS) main.cpp impl1.o impl2.o impl3.o -o main

%.o: %.[ch]
$(CC) $(CC_FLAGS) $< -c

impl1.o: impl1.c
$(CC) $(CC_FLAGS) impl1.c -c

impl2.o: impl2.c
$(CC) $(CC_FLAGS) impl2.c -c

impl3.o: impl3.c
$(CC) $(CC_FLAGS) impl3.c -c

rm -f *.o *~ main *.linkinfo

With the makefile, these commands are executed:

g++ -g -O2 impl1.c -c
g++ -g -O2 impl2.c -c
g++ -g -O2 impl3.c -c
g++ -g -O2 main.cpp impl1.o impl2.o impl3.o -o main

What I have tried with cmake up to now:(you can observe my failed attempts as commented)

add_executable(${PROJECT_NAME} impl1.c impl2.c impl3.c main.cpp)

# add_library(impl1 OBJECT impl1.c)
# add_library(impl2 OBJECT impl2.c)
# add_library(impl3 OBJECT impl3.c)
# add_executable(${PROJECT_NAME} main.cpp $<TARGET_OBJECTS:impl3> $<TARGET_OBJECTS:impl2> $<TARGET_OBJECTS:impl1>)

# add_library(impl1 STATIC impl1.c)
# add_library(impl2 STATIC impl2.c)
# add_library(impl3 STATIC impl3.c)
# add_executable(main main.cpp)
# set(LIBS impl1 impl2 impl3)
# target_link_libraries(main ${LIBS} ${LIBS})

# add_library(headextra impl1.c impl2.c impl3.c)
# add_executable(${PROJECT_NAME} main.cpp)
# target_link_libraries(${PROJECT_NAME} headextra)

# add_library(headextra impl1.c impl2.c impl3.c main.cpp)
# add_executable(${PROJECT_NAME} $<TARGET_OBJECTS:headextra>)

This cmake file successfully generates the build and also compiles. But when linking I got errors that I couldn't resolve:

make -f CMakeFiles/cmake_test.dir/build.make CMakeFiles/cmake_test.dir/build
/usr/bin/cc -o CMakeFiles/cmake_test.dir/impl1.c.o -c /home/ahmet/Desktop/cmake_test/impl1.c
/usr/bin/cc -o CMakeFiles/cmake_test.dir/impl2.c.o -c /home/ahmet/Desktop/cmake_test/impl2.c
/usr/bin/cc -o CMakeFiles/cmake_test.dir/impl3.c.o -c /home/ahmet/Desktop/cmake_test/impl3.c
/usr/bin/c++ -o CMakeFiles/cmake_test.dir/main.cpp.o -c /home/ahmet/Desktop/cmake_test/main.cpp
/usr/bin/cmake -E cmake_link_script CMakeFiles/cmake_test.dir/link.txt --verbose=1
/usr/bin/c++ CMakeFiles/cmake_test.dir/impl1.c.o CMakeFiles/cmake_test.dir/impl2.c.o CMakeFiles/cmake_test.dir/impl3.c.o CMakeFiles/cmake_test.dir/main.cpp.o -o cmake_test -rdynamic
CMakeFiles/cmake_test.dir/main.cpp.o: In function `main':
main.cpp:(.text+0x5): undefined reference to `setup()'
CMakeFiles/cmake_test.dir/main.cpp.o: In function `foo(float, float)':
main.cpp:(.text+0x31): undefined reference to `boo(float*, float*)'
collect2: error: ld returned 1 exit status
make[2]: *** [cmake_test] Error 1
make[1]: *** [CMakeFiles/cmake_test.dir/all] Error 2
make: *** [all] Error

I am definitely missing some key point about cmake build generation. Hopefully, some can help.

Answer Source

That should not work even in the old Makefile, since you are mixing C and C++. The problem is actually unrelated to your build-system and have to do just with the mixing of C and C++.

To solve it change the header files as:

#ifndef HEAD_H
#define HEAD_H

#ifdef __cplusplus
extern "C" {

int foo(float e, float b);
void boo(float *eo, float *eh);
void setup();
int load(int* n);

#ifdef __cplusplus


The big change is that in the header file I have added a check for the macro __cplusplus which will be defined by a C++ compiler and not a C compiler. When __cplusplus is defined, I have told the compiler to treat the function declarations as "C" declarations, which means there will be no name mangling done for the functions.

The problem is that the C++ compiler mangles its function names, to enable function overloading. And that means the symbol for e.g. the function setup is something different when compiling as C++. The extern "C" tells the C++ compiler to not mangle the names.

See e.g. this reference for more information about extern "C".

Also note that I changed the header include guards, as all symbols with double leading underscores are reserved for the "implementation" (i.e. the compiler and standard library) in all scopes. See e.g. this old question and answer.