Mbed-tools and cmake and using mbed-os library in own files

I’m using mbed-tools and cmake to build projects.
Generally you put your source in subfolders. This requires you to put the following in your CMakeLists.txt file (the one in the project root folder):

add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/Application/Generic)
list(APPEND EXTRA_LIBS Generic)
list(APPEND EXTRA_INCLUDES "${CMAKE_CURRENT_SOURCE_DIR}/Application/Generic/include")

target_link_libraries(${APP_TARGET} mbed-os ${EXTRA_LIBS})
target_include_directories(${APP_TARGET} PUBLIC "{EXTRA_INCLUDES}")

And the CMakeLists.txt file in the subfolder looks as such:

add_library(Generic include/code_file.h)
target_include_directories(Generic PUBLIC include)
set_target_properties(Generic PROPERTIES LINKER_LANGUAGE CXX)

If the subfolder requires the mbed-os library, you need to add target_link_libraries(Generic mbed-os)

The problem now it the entire mbed-os is recompiled twice. And this is repeated for every subsequent subfolder.

How do I tell cmake that mbed-os is already built and can be reused?

I’m also working on a project structure for mbed cmake. With mbed CLI1, I had used a structure like

MBedProjects
      |-----mbed-os
      |-----custom-targets
      |-----libs
      |      |------displayStuff
      |      |------otherStuff
      |
      |-----apps
             |-------app1
             |-------app2

not finished yet, but I just managed to mbed-os working in a common folder below my app.
In the CMakeLists.txt of app, you can use:

set(MBED_PATH "../../mbed-os" CACHE INTERNAL "")
set(MBED_CONFIG_PATH ${CMAKE_CURRENT_BINARY_DIR} CACHE INTERNAL "")
set(APP_TARGET testCallback)

include(${MBED_PATH}/tools/cmake/app.cmake)
project(${APP_TARGET})

add_subdirectory(${MBED_PATH}  ${CMAKE_CURRENT_BINARY_DIR}/mbed-os)

so MBED_PATH can be relative to the app. With the Mbed generated CMakeLists.txt, cmake will complain about the outside directory, therefore you have to supply a binary directory in add_subdirectory.

The mbed-tools compile command needs also the path to the mbed-os dir: mbed-tools compile --mbed-os-path ../../mbed-os -t ... -m ...

Maybe it will work also that mbed-os is included in a mbed-os-static lib to build a static lib once.

@hudakz I remember that you also had this problem with an external mbed-os directory.

Awesome! Yes - that’s exactly my folder structure too. I’ll get back on my success. Thank you so much!

What does the CMakeLists.txt file in the root of the project look like?

  • Although the
mbed-tools configure -m ... -t GCC_ARM --mbed-os-path absolute_path_to_mbed-os

command did not report any error, unfortunately, the

mbed-tools compile -m ... -t GCC_ARM --mbed-os-path absolute_path_to_mbed-os

did not work. Since I’m on Linux I cannot tell you whether it works on Windows or Mac.

  • However, the following series of commands (using a symbolic link to the shared mbed-os directory) solved the issue for me on Ubuntu 18.04:
mbed-tools new -c prog01
cd prog01
ln -s absolute_path_to_mbed-os mbed-os
mbed-tools configure -m LPC1768 -t GCC_ARM
cmake -S . -B cmake_build/LPC1768/develop/GCC_ARM -G Ninja
cmake --build cmake_build/LPC1768/develop/GCC_ARM

Yes this is a known issue that will be resolved soon, see:

You can try the patch with mbed_create_distro(), it works great!

My example project is here:

1 Like

I don’t have a CMakeLists in the root, I’m working in one project and e.g. start from app/app1.

yes, because the projects CMakeList needs to be modified to use a different mbed-os path, then it will not matter if Linux or Windows. I will tests this also on Linux, I’m using both.
This is one of the differences between CLI1 and 2, some things that CLI1 managed are now done in cmake. Migration guide - Build tools | Mbed OS 6 Documentation

I don’t have a CMakeLists in the root, I’m working in one project and e.g. start from app/app1.

Neither my root directory of applications (app) contains a CMakeLists.txt file. To create a new ~/app/app1 program I navigate (cd) to the ~/app directory and then I create a new project, as suggested by the CLI2 documentation, by using the command:

mbed-tools new -c app1
...

The shared mbed-os directory could be located for example in the ~/sys directory.

yes, because the projects CMakeList needs to be modified to use a different mbed-os path, then it will not matter if Linux or Windows.

I tried to change the mbed-os path by using the --mbed-os-path option, as suggested in the CLI2 documentation and also manually by editing the CMakeLists.txt file. Unfortunately, neither method worked (on Linux). However, when I kept the CMakeLists.txt file (generated by the mbed-tools new -c app1 command) unchanged and created a symbolic link to the ~/sys/mbed-os directory then the build worked.

tested again with Ubuntu, works like a charm. As on Windows, only faster.

jojo@hp-keller:~/projects/Mbed6-Projects/blinky$ mbed-tools configure -m LPC1768 -t GCC_ARM --mbed-os-path ../mbed-os
mbed_config.cmake has been generated and written to '/home/jojo/projects/Mbed6-Projects/blinky/cmake_build/LPC1768/develop/GCC_ARM/mbed_config.cmake'
jojo@hp-keller:~/projects/Mbed6-Projects/blinky$ mbed-tools compile  -m LPC1768 -t GCC_ARM --mbed-os-path ../mbed-os
Configuring project and generating build system...

important is to change two lines in CMakeLists.txt in the project:

set(MBED_PATH ../mbed-os CACHE INTERNAL "")
add_subdirectory(${MBED_PATH} ${CMAKE_CURRENT_BINARY_DIR}/mbed-os )

it works also with custom_targets, the --custom_targets option needs the full path/filename, not only the path

mbed-tools.exe compile -m STM32F407VE_BLACK -t GCC_ARM --custom-targets-json ../../custom_targets/custom_targets.json --mbed-os-path ../../mbed-os -b develop
1 Like

Thank you Johannes, that works (also with full path to the shared mbed-os) :slight_smile: Actually, I did the first modification. But in my attempts I failed to add ${CMAKE_CURRENT_BINARY_DIR} in the second line. Why we have to add this to make it work?

that is neccessary for the location of the binary file. In cmake_build dir, there is a CMakeFiles/projectname.dir where the compiled object files are stored.
When a lib is in the project tree, then cmake uses this subdirectory also for the object files.
When the lib is somewhere outside, this binary dir info is used for the object files. cmake produces some generated dir names also, don’t know why, but the generated temp dir should not matter.

1 Like

I found some weird behaviour under windows cmake: the obj files are put in strange directory names, which are also used in the compile statistics:

-- built: D:/Projects/Sn/LocalGit/Mbed6-Projects/apps/lvgl8-ST7735/cmake_build/STM32F407VE_BLACK/release/GCC_ARM/lvgl8-ST7735.hex
| Module                                          |           .text |       .data |          .bss |
|-------------------------------------------------|-----------------|-------------|---------------|
| 36e18ecf3f2b452c96fdc670bb9ca99b\cmsis          |       200(+200) |       0(+0) |         0(+0) |
| 72b785fc20e521e56bf35aca1777b714\TARGET_STM32F4 |           0(+0) |       0(+0) |         0(+0) |
| D_\Projects                                     | 124470(+124470) |   495(+495) | 42862(+42862) |
| [fill]                                          |       258(+258) |       5(+5) |       68(+68) |
| [lib]\c.a                                       |     8464(+8464) | 2108(+2108) |       58(+58) |
| [lib]\gcc.a                                     |       772(+772) |       0(+0) |         0(+0) |
| [lib]\misc                                      |       188(+188) |       4(+4) |       28(+28) |
| c0ecae1adab7d4c622418f1e546944e3\libs           |       800(+800) |       0(+0) |     476(+476) |
| c0ecae1adab7d4c622418f1e546944e3\mbed-os        |       326(+326) |       0(+0) |         0(+0) |
| de3935bbbceb118217cc343fec486e94\TARGET_STM32F4 |       638(+638) |       0(+0) |         0(+0) |
| e61ce3a1641df2c26379bb339841fd15\TARGET_STM     |     7584(+7584) |       4(+4) |         0(+0) |
| main.cpp.obj                                    |       988(+988) |       0(+0) |     180(+180) |
| Subtotals                                       | 144688(+144688) | 2616(+2616) | 43672(+43672) |
Total Static RAM memory (data + bss): 46288(+46288) bytes
Total Flash memory (text + data): 147304(+147304) bytes

the same project built under Linux shows correct dirnames, as supplied in add_subdirectory(path binary_path).

can someone confirm? @WillieVisagie which OS are you using?
looks like some cmake problem.

I had the same kind of issue at some point. I think the problem is not really the os, it’s how memap.py parses the information.

It’s expecting a certain formatting and if it can’t find it it will output strange things.

I wanted to make a PR for it but it was too complicated.

no, memap.py is fine, ninja already gets the strange directory names.
I have just opened an issue, but I’ m not sure if it is Mbed or cmake.

I’m not saying memap is not working, it is, but it might expecting stuff to look a certain way.

For example we used to name our library target libSomethingCool and memap would not like that because it looks for the lib string and so the output was completely broken.

Windows might be adding something to the string that breaks the output and shows you the full path instead.

I see the output names from cmake already look like this:

right that’s strange.

I could fix the problem by adding
set(CMAKE_OBJECT_PATH_MAX 512)
to the projects CMakeLists.txt. But this maybe not a generic solution, it must be checked if windows support for filenames longer than 260 chars is enabled and also the toolchain must handle this. For gcc, it seems to be ok.

It seems that the discussion has moved to another direction, but let me try to address the original issue.

As mentioned by Ladislas, static Mbed library is not yet supported. Mbed is compiled as an CMake INTERFACE library, which leads to the issue of recompilation of Mbed for each non-INTERFACE target.

I did not try out the mbed_create_distro(), but it seems to be a workaround for the issue if you are actually building multiple binaries. However, if you are building a single binary, I would recommend declaring your libraries as INTERFACE libraries too.

add_library(MyLibrary
    INTERFACE
)

target_link_libraries(MyLibrary
    INTERFACE
        mbed-os
)

This way, Mbed will not be compiled separately for that library. Of course, this extends the problem if you are building multiple binaries, as your libraries will be compiled multiple times, too. I believe however that this is not the case for most people. Again, this is not the ideal way but another workaround for the issue, so maybe mbed_create_distro() is actually the better way to go.

I’m not convinced that static libs are an overall solution. I have used lvgl and this compiles by default as static lib, but then lto optimization is not applied. This leads to 100+ kb larger binaries.
For my use case where I have one project, but shared Mbed and libs, interface is working. Yes, every project need to compile mbed-os and libs, but the projects can have different options and then a common static will not fit all. For a project with several targets and same mbed-os it will be better, I understand this. But the question is here also about optimization, how is the size for a binary compared to linked with static / interface lib?