Mbed CLI2 config

I’m trying to get familar with the new CLI2. The installation and compiling works so far, but I had to manualy install prettytable, intelhex and future to get rid of all errors.
The mbed-os-example-blinky has missing entries in .gitignore for build and cmake_build.

But more important, how does the config system with mbed_app.json does work together with cmake? I tried to use bare-metal, but compared to the bare-metal example, I had to manually change target_link_libraries to mbed-baremetal in CMakeList.txt, is this correct?
It looks like other settings for the config in mbed_app.json are generated when I call mbed_tools compile. So mbed_tools is always required, I cannot use some cmake configuration itself?
And I’m using VSCode and have installed the CMake Tools extension, but calling the cmake build from the tools gives some errors. How can I use these tools with mbed cmake?
One problem was the large Mbed repo with all targets, the intellisense needed a configuration for all sourcefile path, it looks like this is automatically resolved with cmake include lists, is that right?

1 Like

I found some hints in the Mbed Docs about using and migrating CLI2. Building works so far. Some example for converting user libraries to cmake would be nice.

Now I’m struggling with adding CMakeLists.txt to existing libs. I have cloned lvgl from GitHub - lvgl/lvgl: Embedded graphics library to create beautiful UIs for any MCU, MPU and display type. It's boosted by a professional yet affordable drag and drop UI editor, called SquareLine Studio. and it has also a CMakeLists.txt. After fixing some includes, it compiles with my project when I add

add_subdirectory(lvgl)
target_link_libraries(${APP_TARGET} lvgl)

is this the right way? lvgl is build as a static lib and I can include it and e.g. call lv_init() and it links fine.

Now in the next step, I want to add some driver lib that requires lvgl, but I get errors that lvgl.h is not found. What is necessary to add the lvgl includepath to the lvglDriver lib?

My CMakeLists.txt in lvglDriver:

add_library(lvglDriver INTERFACE)

target_include_directories(lvglDriver
    INTERFACE
        .
)

target_sources(lvglDriver
    INTERFACE
      LVGLDispDriverBase.cpp
      LVGLDispDriver_GC9A01.cpp
      LVGLInputDriverBase.cpp
      LVGLTouchDriverXPT2046.cpp
)

The sources have requirements for mbed.h and lvgl.h.

There are more target dependant sources in TARGET_xy/, but this will be the next question.

But more important, how does the config system with mbed_app.json does work together with cmake?

You can break down mbed-tools compile into three steps:

  • runs mbed-tools configure, which reads the mbed_app.json and generates mbed_config.cmake
  • runs cmake, which configures and generates the build files (in this case Ninja)
  • runs cmake build, which in turn runs Ninja to build the binary.

I tried to use bare-metal, but compared to the bare-metal example, I had to manually change target_link_libraries to mbed-baremetal in CMakeList.txt, is this correct?

I did not try building baremetal with CLI2 yet but sounds reasonable.

It looks like other settings for the config in mbed_app.json are generated when I call mbed_tools compile. So mbed_tools is always required, I cannot use some cmake configuration itself?

Mbed-tools is actually only required to generate mbed_config.cmake, the rest is only a wrapper on top of cmake. After running mbed-tools configure, pure cmake can be used to build the binary.

And I’m using VSCode and have installed the CMake Tools extension, but calling the cmake build from the tools gives some errors. How can I use these tools with mbed cmake?

I use the same setup. The cmake tools extension works pretty well. I use cmake kits to define the compiler and set cmake.buildDirectory to the directory where mbed_config.cmake is generated. There might be some other configurations that I forgot, but try these and post the errors you are getting.

One problem was the large Mbed repo with all targets, the intellisense needed a configuration for all sourcefile path, it looks like this is automatically resolved with cmake include lists, is that right?

Use the "C_Cpp.default.configurationProvider": "ms-vscode.cmake-tools" setting, which makes intellisense to retrieve the build system information from cmake. As an alternative to intellisense, clangd was proposed by @ladislas in another thread but I did not try integrating into my workflow yet.

Now in the next step, I want to add some driver lib that requires lvgl, but I get errors that lvgl.h is not found. What is necessary to add the lvgl includepath to the lvglDriver lib?

You have link lvgl to lvglDriver:

target_link_libraries(lvglDriver lvgl)

Otherwise how do you expect the lvglDriver to find lvgl? You would also need to link it to mbed-os if you want to use mbed functions. This is a pure cmake question and I would recommend reading more on cmake (which unfortunately does not have a very beginner-friendly documentation).

1 Like

The nice thing about clangd is that is uses the compilation database, so only what you need is included and will be available for autocompletion and stuff.

If found clangd much faster than the C/C++ extension from vscode. It requires a little more configuration though.

Our project is now public so you can see how we configure things here:

https://github.com/leka/LekaOS/blob/develop/.vscode/settings.json

1 Like

I think it might also be possible to skip the mbed-tool configure step if you can provide the mbed_config.cmake.

Or at least only need to run it once and have it in your scm so that others on the team can use it

1 Like

thanks a lot for the explanations. I will do more tests tomorrow and write a more detailed answer.

yes, found this also in the documentation. I have 3 different tasks in VSC to call the steps one by one. I was not shure about which is necessary after project creation and changing e.g. mbed_app.json or adding subdirs. Looks like adding dirs is handled by cmake (unless Mbed configuration is used?).
The safest is to use ‘mbed-tools compile’, but that is also the slowest.

ok, have to check this again. The tools want to run cmake in the project root dir, so I will look for this setting.

when ‘mbed-tools configure’ reads the mbed_app.json with ‘requires: bare-metal’, it could possibly create the correct target_link(…). Will also test again.

for sure… I did not understand the INTERFACE library type before, now its clearer: original lvgl cmakelists want to create a static library, so generating a lvgl.a for gcc. I used the same for the lvglDriver, but the static lib needs to resolve includes for compiling. Now I change to INTERFACE (also for lvgl), and lvglDriver files are compiled together with the main project.
I will upload later some project with lvgl and it would be nice if you check the config again. It looks like lvgl cmakelist is also not perfect, I see that the file(GLOB_RECURSE,…) is used and source files are not explicitly added by name. The cmake docs do not recommend to do so.
lvgl has become very popular and cmakelists has already different cases for different environments. The author accepts PRs and lvgl should become also Mbed frindly :slight_smile: I have seen @aglass0fmilk had also used it in components for Mbed, but I haven’t checked if its using cmake already.

ok, long answer already, but I see cmake/ninja is pretty fast and now compiling even on Windows is more fun. Also the depency hell was hard to overcome with CLI1 by using .mbedignore with try and error.
Great stuff!

MSC is using also a compiliation database, but its incredible large with several 10th of GB for every project with every target.
When cmake information is used, that should become also more handy.

Now I change to INTERFACE (also for lvgl), and lvglDriver files are compiled together with the main project.

Interface libraries have disadvantages and are not supposed to be used this way, Mbed will (hopefully) convert to static/module libs in the future, there is a github issue for it somewhere.

It looks like lvgl cmakelist is also not perfect, I see that the file(GLOB_RECURSE,…) is used and source files are not explicitly added by name. The cmake docs do not recommend to do so.

Correct, GLOB_RECURSE is bad practice and lvgl cmakefile has to be improved. It currently works without modification in my project though, there is no need to convert it to interface lib.

Hello guys,

The Mbed CLI 2 documentation says:

Mbed CLI 2. It uses Ninja as a build system, and CMake to generate the build environment and manage the build process in a compiler-independent manner.

Does it mean that when I’d like to use the GCC Arm compiler and linker Ninja will use these tools to build my Mbed bin file?

If that’s the case, what is the advantage of using Ninja over the GNU make tool?

Ninja gets its confiuration (similar to makefiles) from cmake, and for cmake it can be selected somehow which kit to use. The kit is the name for the toolchain there. So, yes this works, cmake adds the calls to gcc. And cmake checks if the compiler tools are installed and working.

Ninja is very fast, didn’t understand yet by which tricks, but thats a reason why this combination is popular for large projects.
One trick is to use dependencies (better) that are generated by the compiler.

yes, because Mr. lvgl accepted my PR to fix the includes :slight_smile:
ok, I will compare to a static lib. My first impression was that the linker could not remove unused stuff, but that was some other mistake.

Thank you Johannes for the quick response. Just to get an idea of the Ninja speed could please let us know the mbed-os-blinky build time (for the same target) when using CLI1 and CLI2?

yes, I can do some comparison a little bit later. I would say it is roughly up to 5 times faster. Especially C++ files compile much faster.
Installing the CLI2 is also no big issue, have done with Win10, will do it also for ubuntu later.
In Win10, you can use ‘winget’, GitHub - microsoft/winget-cli: Windows Package Manager CLI (aka winget)
Then its ‘winget install cmake’ and ‘winget install Arm.GnuArmEmbeddedToolchain’.
And pip install mbed-tools.
Ok, little drawback: MS will now what you are installing.

Now I have compared again the static vs interface lib. The static lib has the problem that my project dependent lv_conf.h is not used and all modules are included. Thats the same problem with mbed as a lib, when it is build static, it has all definable constants fix, like in mbed2 prebuild libs before.
lvgl uses a runtime configuration for the screens, so reducing the code size is only possible by excluding modules from the build.
When lvgl is used as interface lib, no prebuild archive file is generated and I can use the lv_conf.h per project.
So the interface lib cannot be a bad solution.

From my experience working on a large project with a lot of targets/executables to compile, cmake is much faster than CLI1 as you can easily use tools like ccache (https://ccache.dev/) to speed up compilation. And that’s only one of the benefit to use cmake, a lot of other tools can be plugged in easily to make your life easier.

Currently CLI2 as a big issue with the fact that they use INTERFACE for the mbed os parts and that means that if you have more than one target, everything is compiled again for each targets, which takes an awful lot of time.

Until this is fixed (it’s on their to do), I’m still using USCRPL/mbed-cmake

do you have examples for libs that use mbed.h and what is in CMakeLists.txt for the lib?

And the disadvantage of a static lib is that the options are fixed. Mbed has a lot of configurable settings, how do you handle that?
I’m happy alreday with faster compilation than CL1, of course a static mbed-os would even reduce it. But when I do not change things in mbed_app.json, the incremental builds are also pretty fast.

add_library(lvglDriver INTERFACE)

target_include_directories(lvglDriver INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

target_link_libraries(lvglDriver
    INTERFACE
        mbed-os
        lvgl
)

target_sources(lvglDriver
    INTERFACE
      LVGLDispDriverBase.cpp
      LVGLDispDriver_GC9A01.cpp
      LVGLInputDriverBase.cpp
      LVGLTouchDriverXPT2046.cpp
)

I understand that target_include_directories is necessary to export this path as include to the main project. In other examples I see that ‘PUBLIC’ is used there, but INTERFACE works also. What is better?

I have added target_link_libraries because this lib is dependant on mbed-os and lvgl. Is this necessary? I works also without because the main project links mbed-os and lvgl, but to make sure when lvgl is not included in main?

No, because we never include mbed.h, we only include the needed headers to not pollute the global namespace. But with USCRPL/mbed-cmake we still need to link against the whole mbed-os target. That will change with CLI2 and we will be able to link against sub parts of mbed-os.

If you change an option that changes how a target library should behave, it will recompile only what’s needed. Having a very granular architecture makes it easier.

In practice, appart from the first two months of the project, we almost never touch the mbed_app.json. If we do it’s because we are making very important changes, adding a new module, bootloader, and in this case I would deep clean everything before configuring and building again.

We are working on one product, the custom board doesn’t change, so there’s no reason to change the mbed_app.json.

If we need to experiment, we usually do it in separate projects before adding the changes to the main repo.

1 Like

They are not for the same thing. INTERFACE is usually for header-only libraries where nothing must compiled. It also forwards (if that’s the term) all the options to targets linking against the target linking to the interface.

Mbed does that as it was the only way to propagate all the options everywhere. The downside is that if you have two targets, you rebuild everything for each one of them.

For target_include_directories you have PUBLIC, PRIVATE and INTERFACE. I think you must use INTERFACE when doing add_library(xxx INTERFACE)

If you do add_library(xxx STATIC) you’ll be able to use the two others:

I’d say PUBLIC is better.

1 Like

I highly recommend this website:

https://cliutils.gitlab.io/modern-cmake/

thanks, that looks great.

yes, cmake complains when I set target_include_directories to PUBLIC, checked.

when I try to make my lvglDriver component a static lib, it creates a huge library, because I include mbed-os. When I omit the target_link_libraries(mbed-os, then mbed.h cannot be found.

add_library(lvglDriver STATIC)

target_include_directories(lvglDriver INTERFACE ${CMAKE_CURRENT_SOURCE_DIR})

target_link_libraries(lvglDriver
    PRIVATE
        mbed-os
        lvgl
)

target_sources(lvglDriver
    PRIVATE
      LVGLDispDriverBase.cpp
      LVGLDispDriver_GC9A01.cpp
      LVGLInputDriverBase.cpp
      LVGLTouchDriverXPT2046.cpp
)

Yes, it’s because mbed-os is itself an INTERFACE, so it pulls everything each time you have a static target library using it or a target executable. Using INTERFACE for your library will only move the problem down to the target executable. If you have only one, it doesn’t change anything. If you have a dozen, things get complicated.

Yes this is normal. So you don’t have much choice. If lvglDriver uses mbed-os parts, you must make your own library an INTERFACE as you did to avoid the “bloating”.

This is a serious issue for anyone who wants to use mbed-os for something else than just a quick blinky test. I hope it is addressed soon.

@Kojto has branch that started implementing that, GitHub - ARMmbed/mbed-os at feature-cmake-object-libraries and @rwalton opened the following issue: Reduce unnecessary recompilation of object files by removing mbed-os INTERFACE libraries where possible · Issue #15039 · ARMmbed/mbed-os · GitHub

See CMake: From USCRPL/mbed-cmake to Mbed OS first-party CMake · Issue #13981 · ARMmbed/mbed-os · GitHub for a detailed discussion.