Make and build flags
Objectives
Section titled “Objectives”By the end of this lesson, you will be able to
- Enable the SDK’s example catalogue with
ENABLE_PAGE_EXAMPLES=1, and select a CM33-side example to run withSDK_EXAMPLE_CM33. - Explain the difference between
?=and=assignments in Make, and their effect on the build. - Explain why disabled examples add nothing to the firmware size at all.
Takes about 70 minutes (concepts 15 · practice 25 · lab 25 · check 5). The concepts and practice sections only need GNU Make on your computer — nothing to compile.
Before you start
Section titled “Before you start”Two review questions from lesson 2.1.
- Why must
make getlibsbe run in all three projects, rather than once at the top-level folder? - Which line on the serial console is the evidence that an mtb-only board booted successfully?
See it work first
Section titled “See it work first”Open examples/flags.mk. This file doesn’t compile anything — it just prints how make understands each variable. Predict before you run it: how many files will the SOURCES line show in each case? Then run them one at a time.
make -f examples/flags.mkmake -f examples/flags.mk ENABLE_PAGE_EXAMPLES=1 SDK_EXAMPLE_CM33=cm33/io/04_gpio_led_buttonENABLE_PAGE_EXAMPLES=1 make -f examples/flags.mkThe first gives 2 files, the other two give 4, and make also tells you where each value came from (file, command line, environment). Notice two odd-looking lines: FROZEN ends up as just hello even though GREETING is hello world, and TRAILING looks like it’s 1 but shows equals 1: no. These two surprises are what this whole lesson is about.
Concepts
Section titled “Concepts”1. The Makefile structure in the ModusToolbox template
Section titled “1. The Makefile structure in the ModusToolbox template”The template has a two-layer Makefile. The top-level one (Makefile) only says which projects this application has — MTB_TYPE=APPLICATION and MTB_PROJECTS=proj_cm33_s proj_cm33_ns proj_cm55 — then includes ModusToolbox’s build system. Each project’s own Makefile, such as proj_cm33_ns/Makefile, is laid out like this:
| Section | Example from the file | What it does |
|---|---|---|
| Shared values | include ../common.mk |
The variant, the workspace location |
| Name and core | APPNAME=proj_cm33_ns CORE=CM33 |
Tells ModusToolbox which core to build for |
| Components | COMPONENTS+=FREERTOS RTOS_AWARE |
Enables the matching COMPONENT_-prefixed folders |
| Our own variables | ENABLE_PAGE_EXAMPLES ?= 0 |
A default the user can override |
| Passed to C | DEFINES+=ENABLE_PAGE_EXAMPLES=$(ENABLE_PAGE_EXAMPLES) |
Becomes a -D the code can use with #if |
| Excluding a folder | CY_IGNORE += examples |
Stops ModusToolbox from looking for source in that folder |
| Libraries | LDLIBS += .../libbento_hsm.a |
Hands the .a file to the linker |
| At the very end | include $(CY_TOOLS_DIR)/make/start.mk (line 530) |
The build system reads every variable above right here |
Order genuinely matters. The template’s README troubleshooting table records the symptom “Linker cannot find a function from lib/” as being caused by LDLIBS set after include start.mk: “ModusToolbox reads it while including that file; anything later never reaches the linker.”
2. Make variables: =, :=, ?=, +=, and who wins
Section titled “2. Make variables: =, :=, ?=, +=, and who wins”| Written as | Meaning | Expanded when |
|---|---|---|
A = x |
Recursive: stores the text, expands every time it’s used | At use — a variable defined later still takes effect |
A := x |
Simple: expands immediately and stores the result | At that line |
A ?= x |
Assigns only when A is not already defined |
A value from the environment counts as already defined |
A += x |
Appends to the existing value | Following A’s existing type |
A command-line value (make build X=1) beats every assignment inside a file. An environment value loses to = and := in a file, but beats ?=. That’s why the SDK declares ENABLE_PAGE_EXAMPLES ?= 0 and SDK_EXAMPLE_CM33 ?= (lines 321-322). If it were written ENABLE_PAGE_EXAMPLES = 0, the command make build ENABLE_PAGE_EXAMPLES=1 would still work — but a value set in the build machine’s environment would be silently swallowed.
Another thing worth knowing: ifeq is decided the moment make reads that line. A comment in the same file warns that ENABLE_PAGE_BENTO_BUDDY “must be assigned BEFORE the CY_IGNORE ifneq below evaluates it — otherwise the variable is empty at parse time” (lines 60-64). And ifeq compares text literally — a value of 1 with a trailing space is not equal to 1. The SDK’s documentation records this trap as Appendix X #23: a whole feature disappears with no error anywhere. common.mk guards against this by $(strip)-ing the value of BENTO_VARIANT, then deliberately failing the build with $(error ...) if the value isn’t mtb-mpy or mtb-only (lines 80-89), because “a typo must not quietly select the wrong firmware.”
3. Why disabled examples add nothing to the firmware size
Section titled “3. Why disabled examples add nothing to the firmware size”ModusToolbox doesn’t read a source list out of the Makefile — it walks the whole project folder and compiles every file it finds. A comment in CM33’s Makefile explains the consequence.
# Compiled ONLY with ENABLE_PAGE_EXAMPLES=1.## MTB DISCOVERS SOURCES BY WALKING THE APP TREE. `SOURCES +=` adds; it does not# subtract, and it cannot exclude a file the walk already found. Only CY_IGNORE# removes one. Gating a subdirectory with `ifeq (...) SOURCES += wildcard`# therefore does NOTHING — the files compile anyway, and the first symptom is a# wall of "No such file or directory" for headers that subdirectory needs.# Every exclusion below is a CY_IGNORE for that reason.## sdk_examples_cm33_table.c is GENERATED by tools/gen_examples_table.py.ifeq ($(ENABLE_PAGE_EXAMPLES),1)INCLUDES += examples# ... (lines 506-523: excludes a few groups even with examples enabled)elseCY_IGNORE += examplesendifSource: proj_cm33_ns/Makefile lines 494-527 (Apache-2.0, tesaiot-pse84-devkit-sdk); lines 506-523 trimmed for brevity.
When the flag is 0, the whole examples folder is CY_IGNOREd — the files in it never get compiled, so there are no object files, nothing at all for the linker to put in the image. There’s a second layer in the C code itself: main.c calls sdk_examples_cm33_start() only inside #if ENABLE_PAGE_EXAMPLES (lines 384-388). If the flag were off but a stray call remained, the build would fail at the link step — which is better than getting an image with half-finished pieces in it. The catalogue’s README sums it up: with the flag off, “not one byte of this tree reaches the firmware image.”
Worked example
Section titled “Worked example”examples/flags.mk runs in four parts.
- Part 1:
?=sets a default, then prints$(origin ...)to show whether the value came from the file, the command line, or the environment. - Part 2:
GREETING = hello $(WHO)andFROZEN := hello $(WHO)are both declared beforeWHOis set. The first expands at use, so it becomeshello world; the second expands immediately, so it becomeshello. - Part 3: the
SOURCESlist changes withENABLE_PAGE_EXAMPLES, andDEFINESpasses both flags on to the C code. - Part 4:
TRAILING’s value has a trailing space because of a comment at the end of theifeqline, so it isn’t equal to 1 until it’s$(strip)ped.
Try changing things and predicting the result before you run it.
- Move the line
WHO = worldaboveFROZEN := .... What doesFROZENbecome now? - Change
ENABLE_PAGE_EXAMPLES ?= 0toENABLE_PAGE_EXAMPLES = 0, then run all three cases again. Which one changes its result? - Run
make -f examples/flags.mk 'ENABLE_PAGE_EXAMPLES=1 '(note the space inside the quotes). This file accepts it because of$(strip)— what would the real SDK Makefile’sifeq ($(ENABLE_PAGE_EXAMPLES),1)at line 504, which does not strip, do with the same value?
Practice
Section titled “Practice”Open practice/feature.mk. The task is to make feature/feature.c compile only when ENABLE_FEATURE is 1, off by default, overridable from either the command line or the environment, and tolerant of a trailing space. There are 2 gaps to fill in. Check your work with
make -f practice/feature.mk checkBefore fixing anything, you’ll see 2 FAIL lines, and make will end with an error, because check returns a non-zero status whenever even one line says FAIL. (A check that prints FAIL but returns 0 always looks green in CI — we’ll come back to this in lesson 6.2.) Fix each TODO one at a time and run check after each one; you’ll see which line flips from FAIL to PASS.
Solution
Section titled “Solution”Try it yourself for at least 15 minutes first, then open solution/feature.mk. TODO 1 is changing := to ?=, and TODO 2 is starting SOURCES without feature/feature.c, then adding it with += inside ifeq ($(strip $(ENABLE_FEATURE)),1). Notice that the “command line turns it on” line passed even before you fixed anything, because a command-line value beats both := and ?=. A test that passes from the very start doesn’t prove your code is right — it only proves that particular case doesn’t separate a right answer from a wrong one. We’ll come back to this in lesson 6.1.
Check your understanding
Section titled “Check your understanding”Answer the 5 questions in quiz.yaml (shown at the bottom of this page on the website), covering all three objectives. Getting 4 or more right counts as finishing the lesson.
Task: enable the example catalogue, choose one CM33-side example to run, and measure how much enabling examples changes the firmware size.
- Build first with examples disabled (the default), then find CM33_NS’s ELF file and measure the size of each section.
Note down the
Terminal window make build -jfind proj_cm33_ns/build -name "*.elf"arm-none-eabi-size <the .elf file you found>text,dataandbsscolumns. - Build again with examples enabled, and none selected, then measure again.
Flash it with
Terminal window make build -j ENABLE_PAGE_EXAMPLES=1make program, unplug and replug the cable, then read the serial console. The runner will print a list starting=== TESAIoT SDK examples on CM33_NS (...) ===, and tell you how to run one withSDK_EXAMPLE_CM33=<id>. Note the shape of the ids it prints (for example,cm33/io/04_gpio_led_button). - Build again, this time picking one example, such as
SDK_EXAMPLE_CM33=cm33/sensors/01_i2c_bus_scan, and watch for the line[sdk-example] running ...and the result line[sdk-example] ... -> 0 (ok)or another code. - Try a name that doesn’t actually exist, such as the example under “Turning them on” in the catalogue’s README, which uses
tesaiot_hsm/01_acquire_chip. What does the runner say? Compare it with the list the runner prints itself — which one is the truth for this commit? - Compare the sizes from step 1 and step 2. Which section holds the difference, and using concept 3, explain why there’s no such difference when the flag is off.
Evidence to keep in your portfolio: both arm-none-eabi-size results, the example runner’s log (the list, the result of the one you chose, and the result for the nonexistent name), and a short explanation for question 5.
Going further
Section titled “Going further”- The template’s
./bento.sh menusasks make for each flag one at a time instead of reading the Makefile by eye. The template’s README explains why: some flags are set twice, once inside a board condition and once in theelse, and reading the text by eye gets you the wrong answer. Try finding one such flag inproj_cm55/Makefilewithgrep -n "ENABLE_PAGE_" proj_cm55/Makefile. - The GNU Make documentation’s “The Two Flavors of Variables” and “Overriding Variables” sections cover concept 2 in full.
Next lesson: lesson 2.3, Git for firmware work
Reflect
Section titled “Reflect”- If a classmate said “I turned on ENABLE_PAGE_EXAMPLES but nothing changed,” what would be the first three questions you’d ask them?
- Which kind of build flag should fail the build when its value is wrong, and which kind can quietly fall back to a default?
References
Section titled “References”- SDK: the example catalogue (Turning them on, Which core)
- SDK: CM33 non-secure examples (how to run them)
- SDK: proj_cm33_ns/Makefile
- SDK: sdk_examples_cm33_table.c (the list of CM33-side example ids at this commit)
- GNU Make
Review questions
Answer on your own first, then open the answer.
-
You want the CM33 LED-and-button example to run at boot. Which command is right for the commit this course uses? (Objective 1)
- make build ENABLE_PAGE_EXAMPLES=1 SDK_EXAMPLE_CM33=cm33/io/04_gpio_led_button
- make build SDK_EXAMPLE_CM33=cm33/io/04_gpio_led_button (ไม่ต้องเปิดธงอื่น)
- make build ENABLE_PAGE_EXAMPLES=1 SDK_EXAMPLE_CM33=04_gpio_led_button.c
- make build ENABLE_PAGE_EXAMPLES=0 SDK_EXAMPLE_CM33=cm33/io/04_gpio_led_button
Show answer
Answer: A. make build ENABLE_PAGE_EXAMPLES=1 SDK_EXAMPLE_CM33=cm33/io/04_gpio_led_button
ต้องเปิดธงหลัก ENABLE_PAGE_EXAMPLES=1 ก่อน ไม่อย่างนั้นโฟลเดอร์ examples ถูก CY_IGNORE และไม่มีตัวรัน ส่วน id ต้องตรงกับรายการใน sdk_examples_cm33_table.c ซึ่งมีรูปแบบ cm33/<กลุ่ม>/<ชื่อ> ตัวรันเทียบด้วย strcmp ชื่อไฟล์จึงใช้ไม่ได้
-
You build with ENABLE_PAGE_EXAMPLES=1 and leave SDK_EXAMPLE_CM33 empty. What appears on the console? (Objective 1)
- ตัวรันรันตัวอย่างทุกตัวเรียงกัน
- ไม่มีอะไรเลย เพราะไม่ได้เลือกตัวอย่าง
- ตัวรันพิมพ์รายการตัวอย่างทั้งหมดพร้อมวิธีรัน แล้วไม่รันอะไร
- build ล้มเพราะ SDK_EXAMPLE_CM33 ว่าง
Show answer
Answer: C. ตัวรันพิมพ์รายการตัวอย่างทั้งหมดพร้อมวิธีรัน แล้วไม่รันอะไร
Makefile เขียนไว้ว่าค่าว่างคือ list them and run nothing เพราะคำถามแรกของนักพัฒนาคือมีอะไรให้เรียกบ้าง และการไม่รันอะไรโดยปริยายกันไม่ให้การเปิดธงเดียวไปเปิดวิทยุหรือแตะ secure element โดยไม่ตั้งใจ
-
The Makefile has FLAG = 0 and the user runs FLAG=1 make build (environment). What is FLAG, and what if the line were FLAG ?= 0? (Objective 2)
- เป็น 1 ทั้งสองแบบ
- เป็น 0 กับ = และเป็น 1 กับ ?=
- เป็น 1 กับ = และเป็น 0 กับ ?=
- เป็น 0 ทั้งสองแบบ
Show answer
Answer: B. เป็น 0 กับ = และเป็น 1 กับ ?=
ค่าจาก environment แพ้การกำหนดค่าในไฟล์ด้วย = หรือ := แต่ ?= ให้ค่าเฉพาะเมื่อตัวแปรยังไม่ถูกนิยาม และค่าจาก environment นับว่านิยามแล้ว ถ้าส่งเป็น make build FLAG=1 (command line) จะเป็น 1 ทั้งสองแบบ
-
A Makefile has A = x$(B), then C := x$(B), then B = y. What are A and C when used? (Objective 2)
- A = xy และ C = x
- A = x และ C = xy
- ทั้งคู่เป็น xy
- ทั้งคู่เป็น x
Show answer
Answer: A. A = xy และ C = x
= เก็บข้อความไว้และขยายตอนใช้ ซึ่งตอนนั้น B เป็น y แล้ว ส่วน := ขยายทันทีตอนอ่านบรรทัด ขณะนั้น B ยังว่าง
-
Why do the SDK examples add nothing to the image when ENABLE_PAGE_EXAMPLES=0? (choose all that apply) (Objective 3)
- โฟลเดอร์ examples ถูก CY_IGNORE ทั้งโฟลเดอร์ จึงไม่มีไฟล์ใดถูกคอมไพล์
- main.c เรียกตัวรันตัวอย่างเฉพาะใน #if ENABLE_PAGE_EXAMPLES จึงไม่มีการอ้างถึงโค้ดตัวอย่าง
- ตัวอย่างถูกคอมไพล์แล้วบีบอัดใน flash จึงไม่กินที่
- ใช้ ifeq ครอบ SOURCES += ของโฟลเดอร์ examples ก็ตัดไฟล์ออกได้เหมือนกัน
Show answer
Answer: A. โฟลเดอร์ examples ถูก CY_IGNORE ทั้งโฟลเดอร์ จึงไม่มีไฟล์ใดถูกคอมไพล์ · B. main.c เรียกตัวรันตัวอย่างเฉพาะใน #if ENABLE_PAGE_EXAMPLES จึงไม่มีการอ้างถึงโค้ดตัวอย่าง
ModusToolbox หาซอร์สจากการเดินโฟลเดอร์ SOURCES += เพิ่มได้แต่ลบไม่ได้ Makefile ของ SDK จึงบอกว่าการครอบ SOURCES += ด้วย ifeq does NOTHING มีแต่ CY_IGNORE ที่ตัดออก และไม่มีการบีบอัดโค้ดอะไรแบบนั้น
Cite this lesson
If you teach from this lesson or reuse it in slides or documents, credit it with the text below. If you changed it, add (adapted) after the title.
"Make and build flags" from TESA Open Knowledge by the Thai Embedded Systems Association (TESA), https://github.com/tesaiot/tesa-qualification-program, licensed under CC BY-NC 4.0
Thai attribution: "Make และตัวแปรของการ build" จาก TESA Open Knowledge โดยสมาคมสมองกลฝังตัวไทย (Thai Embedded Systems Association: TESA) https://github.com/tesaiot/tesa-qualification-program สัญญาอนุญาต CC BY-NC 4.0
This lesson adapts the source below; keep its credit too.
https://github.com/tesaiot/tesaiot-pse84-devkit-sdk/tree/ef72c1b658178eee8c38b1e47d28b006f80a59b5 · SDK examples and docs are linked at this commit, not copied into this course. Lessons quote short excerpts (at most 25 lines) with a link to the file at this commit and the credit (Apache-2.0, tesaiot-pse84-devkit-sdk).
TESA Open Knowledge · © 2026 สมาคมสมองกลฝังตัวไทย (TESA) · CC BY-NC 4.0
Content is licensed CC BY-NC 4.0. Reuse it non-commercially and credit the Thai Embedded Systems Association (TESA) every time. · How to cite TESA