0

I'm working on a project which requires to develop the firmware for several esp32. All the microcontrollers share a common code that takes care of wifi and mqtt, however they all have a different behavior, which is defined in a specific component. The structure of my project is something like this:

    - CMakeLists.txt
    - Makefile
    - sdkconfig
    - main
        - CMakeLists.txt
        - main.c
    - components
        - wifi_fsm
            - wifi_fsm.h
            - wifi_fsm.c
            - CMakeLists.txt
        - mqtt_fsm
            - mqtt_fsm.h
            - mqtt_fsm.c
            - CMakeLists.txt
        - entity_1
            - entity_1.h
            - entity_1.c
            - CMakeLists.txt
        - entity2
            - entity2.h
            - entity2.c
            - CMakeLists.txt
        ...

Each entity defines some functions with standard names, which implement specific logic for the entity itself and which are called within the shared code (main, wifi_fsm, mqtt_fsm).

void init_entity();                  // called in main.c
void http_get(char *buf);            // called in wifi_fsm
void http_put(char *buf);
void mqtt_msg_read(char *buf);       // called in mqtt_fsm
void mqtt_msg_write(char *buf);

My idea was to have a conditional statement to include at will a specific behavior, so that depending on the entity included, the compiler would link the calls to the functions above to those found in the specific included library. Therefore, at the beginning of main.c I just added the following lines with the goal of having to change the only defined pre-processor symbol to compile for different enity behaviors.

#define ENTITY_1

#ifdef ENTITY_1
  #include "entity_1.h"
#elif defined ENTITY_2
  #include "entity_2.h"
#elif ...
#endif

#include "wifi_fsm.h"
#include "mqtt_fsm.h"

void app_main(void)
{
   while(1){
     ...
   }
}

On the one hand the compiler apparently works fine, giving successful compilation without errors or warnings, meaning that the include chain works correctlty otherwise a duplicate name error for the standard functions would be thrown. On the other hand, it always links against the first entity in alphabetical order, executing for instance the code included in the init_entity() of the component entity_1. If I rename the standard functions in entity_1, then it links against entity_2.

I can potentially use pointers to standard calls to be linked to specific functions in each entity if the approach above is wrong, but I would like to understand first what is wrong in my approach.

EDIT in response to Bodo's request (content of the CMakeFile.txt)

Project:

cmake_minimum_required(VERSION 3.5)

include($ENV{IDF_PATH}/tools/cmake/project.cmake)
project(proj)

Main:

set(COMPONENT_REQUIRES )
set(COMPONENT_PRIV_REQUIRES )

set(COMPONENT_SRCS "main.c")
set(COMPONENT_ADD_INCLUDEDIRS "")

register_component()

Component:

set(COMPONENT_SRCDIRS "src")
set(COMPONENT_ADD_INCLUDEDIRS "include")

set(COMPONENT_REQUIRES log freertos driver nvs_flash esp_http_server mqtt)

register_component()
icarletto
  • 3
  • 4
  • Please [edit] your question and add details about how you define the preprocessor symbol(s) `ENTITY_1` or `ENTITY_2`, etc. You could modify your `CMakeLists.txt` to conditionally use only one of `components/entity_1`, `components/entity_2` etc. Show the relevant `CMakeLists.txt` files. – Bodo Nov 16 '20 at 11:41
  • @Bodo CMakeList.txt are set to default. In case of interdependence between components, I just add the entries in the set(COMPONENT_REQUIRES ...) list – icarletto Nov 16 '20 at 14:32
  • "CMakeList.txt are set to default" doesn't tell anything. When I create a `CMakeLists.txt` with an editor, my "default" is an empty file. If your IDE created the `CMakeLists.txt` files, then tell **in your question** what IDE you use and maybe how you created the project/components if it is relevant for the contents of the files. Show the code instead of vaguely describing what you do. If your files are too big for showing them in your question, create a [mre] with smaller files. Without seeing your `CMakeLists.txt` files I cannot suggest a solution. (Maybe someone else owns a crystal ball...) – Bodo Nov 16 '20 at 15:29
  • @Bodo I don't know what additional information the content of the CMakeFile would add, but I added it to the question. Now, if your answer is to modify the CMakeFile to conditionally include a specific component, this might work and I would appreciate a suggestions on how to do it. However, it still does not answer my question, that is what is wrong in my approach, and why the methods in one component are linked even if the component is not included – icarletto Nov 16 '20 at 17:36
  • Indeed, the `CMakeLists.txt` files don't contain enough information to find out how the program is built. Maybe it is in the included file `project.cmake` or any other included file. I would have to find out how the program is built from the source code. Does the build system create a library for every individual component and link the main object file with the libraries? Then the linker would find a symbol like `init_entity` in the library listed first in the command line. – Bodo Nov 17 '20 at 11:52
  • Do you manually modify `main.c` to choose between `entitry_1` and `entity_2` by specifying a corresponding `#define ENTITY_1` or similar? – Bodo Nov 17 '20 at 11:58
  • Yes exactly, I just change the define to switch between different includes. I designed the code in this way because in my mind it was the easiest solution. Regarding your previous question, I have honestly only a very basic understanding of the building process, but I do see in the `build` folder a separate folder for each entity with the respective `.a` file inside. Also there is a `cmake_install.cmake` that seems to include the install script for each subdirectory. So I guess your analysis of the problem is correct. Any solution? – icarletto Nov 17 '20 at 12:32
  • You should add the information from the comment to the question. Your solution requires modifying the source code to build for different configurations. It might be better to define the symbol on the compiler command line like `-D ENTITY_1` or let CMake do this based on CMake command line options or configuration variables. This way you would be able to build different variants from the same source code. Unfortunately I don't know your build system, so I cannot tell more details how to do this in your case. – Bodo Nov 17 '20 at 12:41

1 Answers1

0

This answer is based on guessing because I don't have enough information. For the same reason it is incomplete in some parts or may not fully match the use case of the question.

The details about how the project will be built seems to be hidden in a cmake include file like project.cmake or nested include files.

My guess is that the build system creates libraries from the source code of every individual component and then links the main object file with the libraries. In this case, the linker will find a symbol like init_entity in the first library that fulfills the dependency. This means the library (=component) listed first in the linker command line will be used.

If the linker command line would explicitly list the object files entity_1.o and entity_2.o, I would expect an error message about a duplicate symbol init_entity.

I can propose two ways to solve the problem:

  1. Make sure only the selected entity is used to build the program.

  2. Make the identifier names unique in all entities and use preprocessor macros to choose the right one depending on the selected entity.


For the first approach you can use conditionals in CMakeLists.txt. See https://stackoverflow.com/a/15212881/10622916 for an example. Maybe the register_component() is responsible for adding the component to the build. In this case you could wrap this in a condition.

BUT modifying the CMakeLists.txt might be wrong if the files are generated automatically.


For the second approach you should rename the identifiers in the entities to make them unique. The corresponding header files can define a macro to replace the common name intended for the identifier with the specific identifier of the selected entity.

In the code that uses the selected entity you will always use the common name, not the individual names.

Example:

entity_1.c:

#include "entity_1.h"

void init_entity_1(void)
{
}

entity_2.c:

#include "entity_2.h"

void init_entity_2(void)
{
}

entity_1.h:

void init_entity_1(void);
// This replaces the token/identifier "init_entity" with "init_entity_1" in subsequent source lines
#define init_entity init_entity_1
// or depending on the parameter list something like
// #define init_entity() init_entity_1()
// #define init_entity(x,y,z) init_entity_1(y,x,z)

entity_2.h:

void init_entity_2(void);
#define init_entity init_entity_2

main.c

#define ENTITY_1

#ifdef ENTITY_1
  #include "entity_1.h"
#elif defined ENTITY_2
  #include "entity_2.h"
#elif ...
#endif


void some_function(void)
{
    init_entity();
}

In this example case with #define ENTITY_1, the preprocessor will change some_function to

void some_function(void)
{
    init_entity_1();
}

before the compilation step and the linker will use init_entity_1 from entity_1.c. An optimizing linker may then omit the object file entity_2.o or the corresponding library because it is unused.

Bodo
  • 9,287
  • 1
  • 13
  • 29
  • Redefining the name of the functions is a good solution, I suppose it works because the functions are renamed only at last stage during the linking. Is this correct? However, since the `#define ENTITY_N` has no global visibility, I had to create a separate header file with the definition of the entity and the conditional inclusion, otherwise it was not possible to use the included libraries from other components. Thanx – icarletto Nov 19 '20 at 08:58
  • @icarletto The implemented functions `init_entity_1` are not renamed. The function call to `init_entity` is replaced textually with `init_entity_1` by the preprocessor before compiling the source code to an object file. I'm not sure I understood the second part of your comment correct. Of course every component that uses any of `entity_*` must (conditionally) include the corresponding header file. – Bodo Nov 19 '20 at 14:52