Search code examples
ccmakelinker

Replace individual function definition based on definitions in toolchain file


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?


Solution

  • 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 printfs 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.