Hi Angus,
Thanks for getting back to us!
ESP_Angus wrote:
Take a CMake library project with an existing CMakeList files and include it in an IDF application without needing to rewrite the CMakeLists file. At the non-project level, this is at least intended as a supported use case (it even has a short section in the docs) but I can see that for existing library projects it's going to fail. Oleg, do you have some examples of particular libraries you're trying this with - is any of the code publically available?
First, let's trade one JSON library with another
I'm using
https://github.com/nlohmann/json
That gets pulled in as-is from github and then in my project I just do (adding it to my previous example above)
Code: Select all
set (JSON_BuildTests OFF CACHE BOOL "")
import_library (json)
target_link_libraries (main utils hash nlohmann_json)
And then things just magically work. That's the way it should be
Another example would be zlib. It also comes with its own CMakeLists.txt. Doesn't cooperate nicely out of the box unfortunately. Requires some patching in the CMakeLists.txt. I'm attaching my modified CMakeLists.txt for zlib. You can diff it against their release version to see my changes, it's not so much actually. I should submit my changes there one day. Anyway, with those, adding it to the app project is as straight forward as before:
Code: Select all
set (ZLIB_SHARED FALSE CACHE BOOL "")
import_library (zlib)
target_link_libraries (main utils hash nlohmann_json zlibstatic)
Another example is libpng. I'm using the public release version as-is and include it like:
Code: Select all
set (PNG_SHARED FALSE CACHE BOOL "")
set (PNG_TESTS FALSE CACHE BOOL "")
set (SKIP_INSTALL_ALL TRUE CACHE BOOL "")
set (AWK "" CACHE STRING "")
import_library (libpng)
target_link_libraries (main utils hash nlohmann_json zlibstatic libpngstatic)
libpng is a bit more advanced. It's got a dependency on libzlib and libm, which it will try to pull in like that in its CMakeLists.txt:
Code: Select all
find_package(ZLIB REQUIRED)
include_directories(${ZLIB_INCLUDE_DIR})
...
find_library(M_LIBRARY
NAMES m
PATHS /usr/lib /usr/local/lib
)
libzlib will be already built by the project and thus will be picked up by cmake naturally. libm however .... in my case, I'm using the "pre-installed" libm from newlib from the cross compiler toolchain build. The problem with all those "installed" libraries are, they are often multilibs, i.e. the actual library will depend on the compiler flags of the whole build. Currently cmake doesn't handle that at all and requires workarounds, but it can be made to work with some additions in the toolchain file.
I hope these can be useful examples for you.
ESP_Agnus wrote:
It's funny, I think this is looking a lot like one of the early development versions of the CMake build. I originally wanted an idf_project macro, but stumbled across the problem where CMAKE_TOOLCHAIN_FILE had to be passed in externally or CMake would add an "implicit" call to project() in the top-level CMakeLists, and initialise with a host toolchain.
I understand why CMake is designed to have the toolchain file as a external cache override - it's a clean design choice for builds which are usually native and only occasionally cross built, usually on another "big" OS not for embedded. But it seems backwards in a cross-compile-oriented build system.
We could argue what's backwards and what is forwards until the cows come home.
I think everybody building this kind of bigger system has a particular view on things. However, I do agree that cmake has some deficits when it comes to cross compiling, especially when it comes to compiling and running generator tools for the
host system as part of the cross build. It totally lacks the notion of host toolchain vs. target toolchain. But it can be mitigated to some extent by using ExternalProject for example.
On top of that, most of the libraries out there are not written to be compiled as part of a bigger project -- cross compiling or not. Instead they are meant to be installed in the OS environment first, then used by other projects. That's one of the reasons why zlib requires some patching for instance. In our case (cross compiling everything from source for some MCU thingy) there is no such thing as "make install"... but that issue can be addressed. Modern examples (nlohmann_json) show that it can work nicely.
ESP_Agnus wrote:
The reason is: we have plans to expand components past their current level of functionality, to have an "IDF package manager" for installing components into a project (so the IDF "core" can slim down a bit, and to make it easier for users to share components with each other).
I totally understand your motivation and where this is heading. Although I'm not sure whether cmake is the right tool for the job. Like you said yourself, it's not really made for it. Maybe leave cmake being cmake and use something else for packet management like ebuild / emerge? Dunno ...
My view on this is, when I write an app, I want to decide which modules I want to use, at which versions and so on. I think git submodules and a top-level CMakeLists.txt for the executable project that manually pulls in all required libraries (either via standard add_subdirectory or some enhanced mechanism) is sufficient to get this done. For instance, in one MCU system, the ESP32 is an optional add-on and is also treated as such by the build. The CMakeLists.txt for the main application (another MCU, not ESP32) looks like that:
Code: Select all
cmake_minimum_required(VERSION 3.0)
include (ExternalProject)
project (wifi_demo)
ExternalProject_Add (esp32
SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/esp32
PREFIX esp32
TMP_DIR esp32
BINARY_DIR esp32
CMAKE_ARGS -DCMAKE_TOOLCHAIN_FILE=${TOOLCHAIN_DIR}/../esp32-idf/toolchain.cmake -DCMAKE_LIBRARIES_DIR=${LIBRARIES_DIR}
BUILD_ALWAYS 1
BUILD_COMMAND $(MAKE)
STEP_TARGETS build flash menuconfig
EXCLUDE_FROM_ALL TRUE
)
ExternalProject_Add_Step (esp32 flash
WORKING_DIRECTORY esp32
COMMAND $(MAKE) flash
DEPENDEES build
)
ExternalProject_Add_Step (esp32 menuconfig
WORKING_DIRECTORY esp32
COMMAND $(MAKE) menuconfig
)
add_executable (wifi_demo.esp32.bin IMPORTED)
set_property (TARGET wifi_demo.esp32.bin PROPERTY IMPORTED_LOCATION "${CMAKE_CURRENT_BINARY_DIR}/esp32/wifi_demo.bin")
add_dependencies (wifi_demo.esp32.bin esp32-build)
import_library (utils)
import_library (board)
import_library (uip)
import_library (net)
import_library (dev)
import_library (hash)
import_library (fs)
import_library (logging)
import_library (version_tag)
set (CMAKE_CXX_STANDARD 14)
set (CMAKE_CXX_STANDARD_REQUIRED 14)
set (CMAKE_CXX__EXTENSIONS OFF)
# enhanced version of the standard "add_executable"
add_target_executable (wifi_demo.elf main.cpp)
add_dependencies (wifi_demo.elf wifi_demo.esp32.bin)
target_link_libraries (wifi_demo.elf
board board_crt stdc++ m c supc++ gcc
uip
net
version_tag
hash
fs
logging
utils
dev
)
# these are additional cmake functions provided by my "version_tag" library
set_default_version_tag_product_name ("WiFi Demo")
add_version_tag (wifi_demo.elf)
add_additional_executable_targets (wifi_demo.elf)
Surely, it's more build-code than the current IDF cmake files, but it also allows for more fine gained control over things, which is necessary for more advanced applications.