When Cursor or VS Code shows Static library, Object library, and Utility in Project Outline, it is describing each target’s build recipe. These labels explain how source files and build actions contribute to the firmware. They do not indicate different firmware images.
This article uses the STM32G474 motor-control project at revision 20247bf as a concrete example. The selected firmware configuration is g474-release-lut-fw.
The four target types visible in Project Outline
| Type | What the build produces | Examples in this project |
|---|---|---|
| Static library | Compiled object files collected into an archive, such as libmotor_current.a | motor_current, motor_encoder, g474_platform |
| Object library | Compiled object files, without an archive of its own | motor_runtime, motor_serial, field_weakening |
| Utility | A build action, such as a check, generation step, or packaging operation | capture_channel_definition, firmware_identity, firmware_candidate |
| Executable | The linked firmware ELF | ch04_vibe_g474 |
The STATIC and OBJECT keywords determine the library recipe. Both compile source code. An object library supplies its objects to a consuming target; a static library collects objects into an archive first. See the CMake library documentation.
A Utility entry here comes from add_custom_target(). It runs commands when requested directly or through a dependency; it can generate files, but it is not a linkable library. See the CMake custom-target documentation.
Static and Object libraries in the actual firmware
For a static library, the intermediate steps look like this:
| |
For an object library, there is no separate archive step:
| |
The .obj extension is what this project’s CMake/Ninja build uses for compiled objects. Other builds may use .o.
An important detail is where those objects go next. In this project, motor_runtime, motor_serial, and the enabled field_weakening module are consumed by g474_platform. Their objects become members of one static archive, Core/libg474_platform.a. Other libraries, including motor_current, have their own archives. The firmware executable links these inputs together.
The source spells out the relationship:
| |
These excerpts belong to separate CMake files. Additional conditional modules are attached elsewhere in the same build configuration. CMake supports consuming object libraries through target_link_libraries(); see its object-library linking rules.
The comment in Core/CMakeLists.txt explains the project’s reason for this arrangement: the runtime and serial callbacks already depend on one another. Giving every such module a separate static archive would introduce additional archive-order concerns. Object targets give those modules their own source lists and compile settings while collecting their objects into the platform archive.
This is a build-organization decision. The runtime’s API, state ownership, and control responsibilities are defined by the source and orchestration contract. A target’s type alone does not establish those boundaries.
Utility targets: work around the compilation and link
The utilities in this project have specific jobs:
| Utility | What it does | Where it participates |
|---|---|---|
motor_tuning_check | Runs the maintained tuning script and produces a report | A prerequisite of the firmware target |
firmware_identity | Prepares the generated firmware identity header | A prerequisite of the compiled targets through the identity-binding logic |
capture_channel_definition | Checks the maintained capture-channel definition | A prerequisite of motor_capture |
firmware_candidate | Checks G474 vectors and packages the firmware candidate | Depends on the firmware executable; included in the default build with ALL |
For example, the capture module declares:
| |
add_dependencies() establishes build ordering. It does not insert the utility into the firmware’s link inputs.
A custom target is considered out of date whenever it is requested, so its commands can run on successive builds even when no C file needs recompiling. ALL includes a target in the default build; it does not mean every target in the project runs on every command. For file generation that should depend on whether an output is stale, CMake also provides add_custom_command(OUTPUT ...). These distinctions are described in the custom-target reference.
A preset selects the firmware; a target describes one part of its build
g474-release-lut-fw is a preset, not a library. It selects the build directory and configuration flags. Here it inherits the speed-loop configuration and enables field weakening, the current-reference notch, fuzzy speed Kp, and CCM control placement.
The relationship is:
| |
For this preset, the ELF is:
| |
The executable target retains the historical name ch04_vibe_g474. Other presets can produce an ELF with the same filename in their own build directories. The directory, selected configuration, and firmware identity distinguish the images.
The orchestration selection also happens at configure time. cmake/motor_orchestration.cmake attaches exactly one control-profile source. LUT-FW selects motor_profile_encoder.c and the binary serial plot implementation. The winding-experiment configuration selects the winding profile and text plot implementation. These are compile-time choices.
Rules for reading and maintaining this project
- Read the target declaration first.
add_library(... STATIC ...),add_library(... OBJECT ...),add_custom_target(...), andadd_executable(...)determine the type. A folder name or module name does not. - Follow the consumer relationship.
target_link_libraries()identifies how libraries and objects reach the firmware.add_dependencies()orders build actions. Inspect the generated link/archive commands when the distinction matters. - Keep compile requirements on the owning target.
PRIVATEapplies requirements to that target,PUBLICalso passes them to consumers, andINTERFACEpasses them only to consumers. The project usesmotor_module_config, anINTERFACElibrary, to share configuration requirements without compiling its own implementation. See CMake’s usage-requirement rules. - Respect the maintained source owner. Each module owns its build recipe. Generated controller code is updated through its maintained generation entry, rather than by editing generated output. Utility checks and identity generation belong to the build graph.
- Inspect the final ELF and map for memory questions. Object libraries do not guarantee that every function survives linking. This firmware uses section garbage collection; archive extraction and final section retention are separate steps. FLASH, RAM, and CCM placement are determined by section and linker rules, including the project’s CCM configuration. See the GNU linker options.
For future work, preserve the reason behind the existing target type. Independent archive-based modules such as motor_current already use STATIC; runtime-composition modules use OBJECT to participate in the shared platform archive; checks and packaging use Utility targets. A new target should express its actual output and dependencies rather than copy a neighboring label.
Project examples were checked against the G474 repository at revision 20247bf, including the root and module CMake files, orchestration documentation, presets, and generated Ninja archive/link rules. Later revisions may change individual targets or configuration flags.