I am making a library which will define a number of functions. There will be a default version of each function, and then the user may provide a file containing alternative definitions of each function, optimized for a certain hardware target.
I have a header file eg operations.h
:
//operations.h
#ifndef OPERATIONS_H
#define OPERATIONS_H
#define BASE_OPERATION_1
int operation_1(int a, int b);
#define BASE_OPERATION_2
int operation_2(int a, int b);
#endif
Then the default implementations in a source file:
//base/operations.c
#ifdef BASE_OPERATION_1
int op_1(int a, int b)
{
return a + b;
}
#endif
#ifdef BASE_OPERATION_2
int op_2(int a, int b)
{
return 2 * (a + b);
}
#endif
I then have custom implementations in a separate source file. There may not be a replacement for every function.
// custom/operations.c
#ifdef CUSTOM_OPERATION_2
#undef BASE_OPERATION_2
int op_2(int a, int b)
{
return 3 * (a + b);
}
#endif
Then in a toolchain file I can do
set(CUSTOM_SOURCES custom/operations.c)
add_definitions( -DCUSTOM_OPERATION_2 )
The above solution results in a "duplicate symbol" error. I think because the preprocessor does not see both .c files at the same time, and so the undef
does not achieve the behaviour I want. They are in different translation units so the undef is never seen when compiling the base file.
Can anyone tell me a way to get this behavior?
To solve your problem, remove all preprocessor
directives concerning BASE_OPERATION_N
and rewrite source files like so:
//operations.h
#ifndef OPERATIONS_H
#define OPERATIONS_H
int op_1(int a, int b);
int op_2(int a, int b);
#endif
//base/operations.c
#include <operations.h>
#ifndef CUSTOM_OP_1
int op_1(int a, int b)
{
return a + b;
}
#endif
#ifndef CUSTOM_OP_2
int op_2(int a, int b)
{
return 2 * (a + b);
}
#endif
// custom/operations.c
#ifdef CUSTOM_OP_2
int op_2(int a, int b)
{
return 3 * (a + b);
}
#endif
With this, -DCUSTOM_OP_2
suffices to disable compilation of base op_2
and
enable the compilation of custom op_2
. Conversely the absence of -DCUSTOM_OP_2
suffices to disable the compilation of custom op_2
and enable the compilation
of base op_2
.
Here's a demo. My CMake project is:
$ tree
.
├── base
│ └── operations.c
├── CMakeLists.txt
├── config.cmake
├── custom
│ └── operations.c
├── operations.h
└── test.c
cmake.config
is my toolchain file. It doesn't need to exist for the
default build. I'm building a test program as well as the library to prove this works. For the
same purpose I'm putting some printf
s in the sources:
$ cat base/operations.c
//base/operations.c
#include <stdio.h>
#include <operations.h>
#ifndef CUSTOM_OP_1
int op_1(int a, int b)
{
printf("%s_%s\n","Base",__func__);
return a + b;
}
#endif
#ifndef CUSTOM_OP_2
int op_2(int a, int b)
{
printf("%s_%s\n","Base",__func__);
return 2 * (a + b);
}
#endif
$ cat custom/operations.c
// custom/operations.c
#include <stdio.h>
#ifdef CUSTOM_OP_1
int op_1(int a, int b)
{
printf("%s_%s\n","Custom",__func__);
return 3 * (a + b);
}
#endif
#ifdef CUSTOM_OP_2
int op_2(int a, int b)
{
printf("%s_%s\n","Custom",__func__);
return 3 * (a + b);
}
#endif
My CMakeLists.txt
is:
$ cat CMakeLists.txt
cmake_minimum_required(VERSION 3.14)
project(operations)
set(BASE_SOURCES ./base/operations.c)
set(SOURCES ${BASE_SOURCES} ${CUSTOM_SOURCES})
include_directories(./)
add_library(
operations
${SOURCES}
)
add_executable(
test
test.c)
target_link_libraries(test operations)
The default build goes:
$ mkdir build
$ cd build
$ cmake ..
-- The C compiler identification is GNU 13.2.0
-- The CXX compiler identification is GNU 13.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.2s)
-- Generating done (0.0s)
-- Build files have been written to: /home/imk/develop/so/cmake_prob/build
$ make
[ 25%] Building C object CMakeFiles/operations.dir/base/operations.c.o
[ 50%] Linking C static library liboperations.a
[ 50%] Built target operations
[ 75%] Building C object CMakeFiles/test.dir/test.c.o
[100%] Linking C executable test
[100%] Built target test
And the program runs:
$ ./test
Base_op_1
Base_op_2
Now here's my toolchain file v. 1:
$ cd ..
$ cat config.cmake
set(CUSTOM_SOURCES ./custom/operations.c)
add_compile_options(-DCUSTOM_OP_2)
I'm disabling base op_2
, enabling custom op_2
Zap and regenerate the build system:
$ cd build
$ rm -fr *
$ cmake -DCMAKE_TOOLCHAIN_FILE=config.cmake ..
-- The C compiler identification is GNU 13.2.0
-- The CXX compiler identification is GNU 13.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done (0.2s)
-- Generating done (0.0s)
-- Build files have been written to: /home/imk/develop/so/cmake_prob/build
Rebuild and test:
$ make && ./test
[ 20%] Building C object CMakeFiles/operations.dir/base/operations.c.o
[ 40%] Building C object CMakeFiles/operations.dir/custom/operations.c.o
[ 60%] Linking C static library liboperations.a
[ 60%] Built target operations
[ 80%] Building C object CMakeFiles/test.dir/test.c.o
[100%] Linking C executable test
[100%] Built target test
Base_op_1
Custom_op_2
There's the custom op_2
.
Now I'd like to revert to base op_2
and enable custom op_1
. The tool chain file for that is v.2:
$ cd ..
$ cat config.cmake
set(CUSTOM_SOURCES ./custom/operations.c)
add_compile_options(-DCUSTOM_OP_1)
Don't need to regenerate the build system now. I can do:
$ cd build
$ make
-- Configuring done (0.0s)
-- Generating done (0.0s)
-- Build files have been written to: /home/imk/develop/so/cmake_prob/build
[ 20%] Building C object CMakeFiles/operations.dir/base/operations.c.o
[ 40%] Building C object CMakeFiles/operations.dir/custom/operations.c.o
[ 60%] Linking C static library liboperations.a
[ 60%] Built target operations
[ 80%] Building C object CMakeFiles/test.dir/test.c.o
[100%] Linking C executable test
[100%] Built target test
$ ./test
Custom_op_1
Base_op_2
There's the custom op_1
.