Zephyr base improvement / improved Zephyr user experience

Rasmussen, Torsten

Hi All,


I would like to inform you of a suggested improvement on how Zephyr is used in an application and how to source boilerplate.cmake code.



Today, each application contains:
include($ENV{ZEPHYR_BASE}/cmake/app/boilerplate.cmake NO_POLICY_SCOPE)


My proposal is to create a Zephyr CMake config package, which allows a user to simply write:


in the future.


But let me describe some reason for suggesting this change.


The existing solution works, and for Linux users, it is very common to use environment variables.

However, this solution also have some drawbacks.


One drawback, is that developers working both up-/ and downstream may not be able to always has a single ZEPHYR_BASE, and thus they must still remember to source zephyr-env.sh or run zephyr-env.cmd before working.


Second drawback is people using IDEs such as Eclipse or SES, must ensure that ZEPHYR_BASE is correctly set BEFORE launching the IDE.

If only having one Zephyr, that is not a big issue, but having out-of-tree applications depending on different Zephyr versions or working both up- / downstream does make this a bit more annoying.


Especially many Windows users are not fond of running a command script prior to launching their IDE.


Third drawback, is the fact that ZEPHYR_BASE is not sticky after first CMake invocation, as example, if you try the following:

$ source /tmp/zephyr-1/zephyr/zephyr-env.sh

$ cd <path-to-sample>/hello_world

$ mkdir build; cd build

$ cmake -GNinja -DBOARD=nrf52840_pca10056 ..

-- Zephyr version: 2.2.0-rc2

-- Found PythonInterp: /usr/bin/python3.6 (found suitable version "3.6.8", minimum required is "3.6")

Loaded configuration '/tmp/zephyr-1/zephyr/boards/arm/nrf52840_pca10056/nrf52840_pca10056_defconfig'

-- Build files have been written to: /tmp/zephyr-1/zephyr/samples/hello_world/build

$ ninja

[122/122] Linking C executable zephyr/zephyr.elf


# This could be similar to a user changing folder to do something else,

# or working in an IDE where the ZEPHYR_BASE is different from the value on first CMake invocation.

# and therefore has sourced a different Zephyr

$ source /tmp/zephyr-2/zephyr/zephyr-env.sh

# Indicate a CMakeLists.txt change causing CMake re-run on next ninja invocation.

$ touch ../CMakeLists.txt

$ ninja

[0/1] Re-running CMake...

-- Zephyr version: 2.1.99

-- Selected BOARD nrf52840_pca10056

-- Found west: /home/<user>/.local/bin/west (found suitable version "0.7.0", minimum required is "0.6.0")

-- Loading /tmp/zephyr-1/zephyr/boards/arm/nrf52840_pca10056/nrf52840_pca10056.dts as base

Error: nrf52840_pca10056.dts.pre.tmp:394.23-24 syntax error

FATAL ERROR: Unable to parse input tree

CMake Error at /tmp/zephyr-2/zephyr/cmake/dts.cmake:188 (message):

  command failed with return code: 1

Call Stack (most recent call first):

  /tmp/zephyr-2/zephyr/cmake/app/boilerplate.cmake:460 (include)

  CMakeLists.txt:5 (include)


-- Configuring incomplete, errors occurred!

See also "/tmp/zephyr-1/zephyr/samples/hello_world/build/CMakeFiles/CMakeOutput.log".

See also "/tmp/zephyr-1/zephyr/samples/hello_world/build/CMakeFiles/CMakeError.log".

FAILED: build.ninja

/opt/cmake-3.14.4-Linux-x86_64/bin/cmake -S/tmp/zephyr-1/zephyr/samples/hello_world -B/tmp/zephyr-1/zephyr/samples/hello_world/build

ninja: error: rebuilding 'build.ninja': subcommand failed


As seen above, the Zephyr in used is now in mixed state and thus failing.


I have therefore made the following PR, which solve both above mentioned problems, as well as give a better user experience.


It has the following advantages:

  1. CMake will use `find_package` to locate the right Zephyr
  2. Version can be used in an application to ensure the correct Zephyr version for Zephyr users when having multiple Out-of-tree apps
  3. ZEPHYR_BASE is sticky after first CMake invocation, thus if Zephyr based is changed after first run, it will not impact subsequent CMake invocations (directly or through ninja).
  4. Allows to have multiple projects with different Zephyr bases in same IDE, as example Eclipse.
  5. Easier getting started for developers. No boilerplate sourcing, no ZEPHYR_BASE / zephyr-env.sh sourcing, just put:
  6. Fully backward compatible with existing ZEPHYR_BASE.



So a Zephyr sample such as Hello World may look as:

# SPDX-License-Identifier: Apache-2.0


cmake_minimum_required(VERSION 3.13.1)


find_package(Zephyr 2.2.0 HINTS $ENV{ZEPHYR_BASE})



target_sources(app PRIVATE src/main.c)



And a downstream user creating an app based upon Zephyr without version check, can simply do:



If a user wants to support the possibility of overriding the Zephyr by manually specifying ZEPHYR_BASE, the following can be used:  

`find_package(Zephyr HINTS $ENV{ZEPHYR_BASE})`


A user requiring Zephyr version 2.2.0 or above can do:

`find_package(Zephyr 2.2.0)`


A user requiring an exact Zephyr version 2.2.0 can do:

`find_package(Zephyr 2.2.0 EXACT)`



To show that Zephyr base is now sticky, I have re-tried above failure using the new principle:

$ source /tmp/zephyr-1/zephyr/zephyr-env.sh

$ mkdir build; cd build

$ cmake -GNinja -DBOARD=nrf52840_pca10056 ..

Including boilerplate (zephyr base): /tmp/zephyr-1/zephyr/cmake/app/boilerplate.cmake

-- Zephyr version: 2.2.0-rc2

-- Found PythonInterp: /usr/bin/python3.6 (found suitable version "3.6.8", minimum required is "3.6")

Loaded configuration '/tmp/zephyr-1/zephyr/boards/arm/nrf52840_pca10056/nrf52840_pca10056_defconfig'

-- Build files have been written to: /tmp/zephyr-1/zephyr/samples/hello_world/build

$ ninja

[119/124] Linking C executable zephyr/zephyr_prebuilt.elf

Memory region         Used Size  Region Size  %age Used

           FLASH:       11484 B         1 MB      1.10%

            SRAM:        5180 B       256 KB      1.98%

        IDT_LIST:          56 B         2 KB      2.73%

[124/124] Linking C executable zephyr/zephyr.elf


$ source /tmp/zephyr-2/zephyr/zephyr-env.sh

$ touch ../CMakeLists.txt

$ ninja

[0/1] Re-running CMake...

Including boilerplate (zephyr base (cached)): /tmp/zephyr-1/zephyr/cmake/app/boilerplate.cmake

-- Zephyr version: 2.2.0-rc2

-- Loading /tmp/zephyr-1/zephyr/boards/arm/nrf52840_pca10056/nrf52840_pca10056.dts as base

Loaded configuration '/tmp/zephyr-1/zephyr/samples/hello_world/build/zephyr/.config'

No change to configuration in '/tmp/zephyr-1/zephyr/samples/hello_world/build/zephyr/.config'

No change to Kconfig header in '/tmp/zephyr-1/zephyr/samples/hello_world/build/zephyr/include/generated/autoconf.h'

-- Build files have been written to: /tmp/zephyr-1/zephyr/samples/hello_world/build

[86/90] Linking C executable zephyr/zephyr_prebuilt.elf

[90/90] Linking C executable zephyr/zephyr.elf




Also a small screenshot from Eclipse to show that two projects can be imported with different Zephyr bases, and both be built and using their own Zephyr base (and in  this example own requested Zephyr version):



If anyone is interested in a presentation of the new possibilities, I will be happy to make a small presentation for next TSC meeting.



Torsten Tejlmand Rasmussen

Senior R&D Engineer

P: +47 72 89 92 47




Nordic Semiconductor

Otto Nielsens veg 12, 7052 Trondheim, Norway




SM_symbol_FB  nordic_symbol_small_TW  nordic_symbol_small_YT_ny2  nordic_symbol_small_IN  





Rasmussen, Torsten

To test the PR: https://github.com/zephyrproject-rtos/zephyr/pull/23054

Just checkout the PR in an existing Zephyr project workspace, and run the extension command:

west zephyr-export

Then you can try build any sample.
Note, this is a one time command to run.