Skip to content

Commit c731f84

Browse files
emraineyCopilot
andcommitted
Fix host/unit test linker regressions in CI matrix
What's Wrong: * Host unit test links were unstable across GCC/LLVM and container environments. * Board-linked unit tests could miss transitive symbols or pull too much code depending on linker behavior, causing undefined references. * GCC hit a constructor-call mismatch in catch2-buffer where Clang passed. How Was it Fixed (if not obvious): * Kept whole-archive usage narrow to board archive handling in host test linking, avoiding broad whole-archive on module libraries. * Added a weak default core printer implementation so forced object pull-in has a safe fallback when tests do not provide one. * Updated core module sources to include that printer implementation. * Removed explicit nullptr constructor argument in catch2-buffer and used the default parameter path accepted by GCC. What side effects does this have (could be none): * Link behavior is more deterministic for board-linked host tests. * Non-board module overrides/mocks are less likely to collide with whole-archive behavior. * No intended runtime behavior change on target firmware paths. Which builds did you run to make sure they build? [x] arm-none-eabi-gcc Cortex M4 [x] arm-none-eabi-gcc Cortex M7 [ ] (Apple) Native Clang [ ] (Apple) Homebrew GCC [x] (Apple) Homebrew LLVM How Do We Know and Can Show It's Fixed: * Ubuntu container workflow presets passed: on-host-native-gcc, on-host-native-llvm, on-target-cortex-m4-gcc-arm-none-eabi-ci, on-target-cortex-m7-gcc-arm-none-eabi-ci. * Host-side cleanup was done before container runs to avoid stale build artifacts. Which Unittest Series did you Check? [ ] (Apple) Native Clang [x] (Apple) Homebrew GCC [x] (Apple) Homebrew LLVM Did this affect any on-target builds? If so which were tested? [x] STM32F407VE board [x] STM32H753ZI board AI/Human Contribution: * AI generated the linker/test compatibility changes and executed container validation runs. * Human selected validation strategy, confirmed results, and approved commit text. Co-authored-by: Copilot <copilot@github.com>
1 parent 438bf7b commit c731f84

43 files changed

Lines changed: 1033 additions & 437 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 71 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -2,136 +2,137 @@ name: CI Build
22

33
on:
44
push:
5-
branches: [main, develop]
5+
branches: [ main, develop ]
66
pull_request:
7-
branches: [main, develop]
7+
branches: [ main, develop ]
88
workflow_dispatch:
99

1010
jobs:
1111
build-arm-cortex-m4:
12-
name: Build Cortex-M4 (arm-none-eabi)
12+
name: Build Cortex-M4 (arm-none-eabi) on ${{ matrix.distro }}
1313
runs-on: ubuntu-latest
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
distro: [ ubuntu ]
18+
container:
19+
image: ghcr.io/emrainey/bare-metal-dev-ubuntu:latest
20+
defaults:
21+
run:
22+
shell: sh
1423

1524
steps:
16-
- name: Install latest CMake
17-
uses: lukka/get-cmake@latest
18-
19-
- name: Install ARM GNU Toolchain
20-
uses: carlosperate/arm-none-eabi-gcc-action@v1
21-
with:
22-
release: "13.2.Rel1"
23-
24-
- name: Install build dependencies
25-
run: |
26-
sudo apt-get update
27-
sudo apt-get install -y ninja-build
28-
29-
- name: Verify ARM toolchain
30-
run: arm-none-eabi-gcc --version
31-
3225
- name: Checkout repository
3326
uses: actions/checkout@v4
3427
with:
3528
submodules: recursive
3629

30+
- name: Verify toolchain
31+
run: |
32+
cmake --version
33+
ninja --version
34+
arm-none-eabi-gcc --version
35+
3736
- name: Configure and Build Cortex-M4
38-
run: cmake --workflow --preset on-target-cortex-m4-gcc-arm-none-eabi
37+
run: cmake --workflow --preset on-target-cortex-m4-gcc-arm-none-eabi-ci
3938

4039
build-arm-cortex-m7:
41-
name: Build Cortex-M7 (arm-none-eabi)
40+
name: Build Cortex-M7 (arm-none-eabi) on ${{ matrix.distro }}
4241
runs-on: ubuntu-latest
42+
strategy:
43+
fail-fast: false
44+
matrix:
45+
distro: [ ubuntu ]
46+
container:
47+
image: ghcr.io/emrainey/bare-metal-dev-ubuntu:latest
48+
defaults:
49+
run:
50+
shell: sh
4351

4452
steps:
45-
- name: Install latest CMake
46-
uses: lukka/get-cmake@latest
47-
48-
- name: Install ARM GNU Toolchain
49-
uses: carlosperate/arm-none-eabi-gcc-action@v1
50-
with:
51-
release: "13.2.Rel1"
52-
53-
- name: Install build dependencies
54-
run: |
55-
sudo apt-get update
56-
sudo apt-get install -y ninja-build
57-
58-
- name: Verify ARM toolchain
59-
run: arm-none-eabi-gcc --version
60-
6153
- name: Checkout repository
6254
uses: actions/checkout@v4
6355
with:
6456
submodules: recursive
6557

58+
- name: Verify toolchain
59+
run: |
60+
cmake --version
61+
ninja --version
62+
arm-none-eabi-gcc --version
63+
6664
- name: Configure and Build Cortex-M7
67-
run: cmake --workflow --preset on-target-cortex-m7-gcc-arm-none-eabi
65+
run: cmake --workflow --preset on-target-cortex-m7-gcc-arm-none-eabi-ci
6866

6967
build-native-gcc:
70-
name: Build and Test (native GCC)
68+
name: Build and Test (native GCC) on ${{ matrix.distro }}
7169
runs-on: ubuntu-latest
70+
strategy:
71+
fail-fast: false
72+
matrix:
73+
distro: [ ubuntu ]
74+
container:
75+
image: ghcr.io/emrainey/bare-metal-dev-ubuntu:latest
76+
defaults:
77+
run:
78+
shell: sh
7279

7380
steps:
74-
- name: Install latest CMake
75-
uses: lukka/get-cmake@latest
76-
77-
- name: Install dependencies
78-
run: |
79-
sudo apt-get update
80-
sudo apt-get install -y ninja-build gcc g++
81-
82-
- name: Verify GCC
83-
run: gcc --version
84-
8581
- name: Checkout repository
8682
uses: actions/checkout@v4
8783
with:
8884
submodules: recursive
8985

86+
- name: Verify toolchain
87+
run: |
88+
cmake --version
89+
ninja --version
90+
gcc --version
91+
9092
- name: Configure, Build, and Test
9193
run: cmake --workflow --preset on-host-native-gcc
9294

9395
- name: Upload test results
9496
if: always()
9597
uses: actions/upload-artifact@v4
9698
with:
97-
name: test-results-gcc
99+
name: test-results-gcc-${{ matrix.distro }}
98100
path: build/native-gcc/Testing/**/*.xml
99101
if-no-files-found: ignore
100102

101103
build-native-llvm:
102-
name: Build and Test (native LLVM)
104+
name: Build and Test (native LLVM) on ${{ matrix.distro }}
103105
runs-on: ubuntu-latest
106+
strategy:
107+
fail-fast: false
108+
matrix:
109+
distro: [ ubuntu ]
110+
container:
111+
image: ghcr.io/emrainey/bare-metal-dev-ubuntu:latest
112+
defaults:
113+
run:
114+
shell: sh
104115

105116
steps:
106-
- name: Install latest CMake
107-
uses: lukka/get-cmake@latest
108-
109-
- name: Install LLVM toolchain
110-
run: |
111-
wget https://apt.llvm.org/llvm.sh
112-
chmod +x llvm.sh
113-
sudo ./llvm.sh 20
114-
sudo apt-get update
115-
sudo apt-get install -y ninja-build
116-
sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-20 100
117-
sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-20 100
118-
sudo update-alternatives --install /usr/bin/lld lld /usr/bin/lld-20 100
119-
120-
- name: Verify LLVM
121-
run: clang --version
122-
123117
- name: Checkout repository
124118
uses: actions/checkout@v4
125119
with:
126120
submodules: recursive
127121

122+
- name: Verify toolchain
123+
run: |
124+
cmake --version
125+
ninja --version
126+
clang --version
127+
clang++ --version
128+
128129
- name: Configure, Build, and Test
129130
run: cmake --workflow --preset on-host-native-llvm
130131

131132
- name: Upload test results
132133
if: always()
133134
uses: actions/upload-artifact@v4
134135
with:
135-
name: test-results-llvm
136+
name: test-results-llvm-${{ matrix.distro }}
136137
path: build/native-llvm/Testing/**/*.xml
137138
if-no-files-found: ignore

AGENTS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,5 +66,7 @@ Make sure that the cross builds are not broken either by building them:
6666

6767
-**Always do** make unit tests for new code within the same project. Prefer `catch2` for basic objects or template with no dependencies and `googletest` for anything that requires abstract interfaces. All abstract interfaces must be created with mocks within the `test/mocks` folder following the same include folder hierarchy as the original interface.
6868
-**Always do** When summarizing work for commit message mention what work was done by the AI and what work was done by a human. For example, "AI generated unit tests for the `Foo` class, while a human wrote the commit message and reviewed the tests."
69+
-**Allowed** to delete files under the build folder (and recursively therein) (`rm -rf build/*`) for the purposes of cleaning the build, but **NEVER** delete files under any other folder without explicit permission from a human.
70+
-**Always do** clean the build artifacts from outside any container you are using. Permissions inside the container may not allow it.
6971
- ⚠️ **Ask First** before modifying source code or documentation in a major way.
7072
- 🚫 **NEVER** modify the git repository or the .git folder.

CMakeLists.txt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,17 @@ set(CMAKE_CXX_STANDARD_REQUIRED TRUE)
1515
set(CMAKE_CXX_STANDARD 20)
1616
set(CMAKE_C_STANDARD 17)
1717

18-
set(CMAKE_CXX_LINK_GROUP_USING_RESCAN_SUPPORTED TRUE)
19-
if (CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 11)
20-
message(STATUS "Using GNU C++ Link Group with RESCAN")
18+
if ((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER_EQUAL 11)
19+
OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND CMAKE_SYSTEM_NAME STREQUAL "Linux"))
20+
message(STATUS "Using C++ Link Group with RESCAN")
21+
set(CMAKE_CXX_LINK_GROUP_USING_RESCAN_SUPPORTED TRUE)
2122
set(CMAKE_CXX_LINK_GROUP_USING_RESCAN
2223
"LINKER:--start-group"
2324
"LINKER:--end-group")
2425
else()
25-
message(STATUS "Not using GNU C++ Link Group with RESCAN")
26+
message(STATUS "Not using C++ Link Group with RESCAN")
27+
set(CMAKE_CXX_LINK_GROUP_USING_RESCAN_SUPPORTED FALSE)
28+
2629
set(CMAKE_CXX_LINK_GROUP_USING_RESCAN FALSE)
2730
endif()
2831
set(CMAKE_CXX_SCAN_FOR_MODULES OFF)

CMakePresets.json

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,11 +72,27 @@
7272
}
7373
},
7474
{
75-
"name": "on-host",
75+
"name": "on-target-ci",
76+
"hidden": true,
7677
"inherits": [
7778
"common",
7879
"graph_preset"
7980
],
81+
"displayName": "On Target Build (CI)",
82+
"description": "Builds the project on the target device in CI without distcc",
83+
"cacheVariables": {
84+
"BUILD_SHARED_LIBS": "OFF",
85+
"BUILD_UNIT_TESTS": "OFF",
86+
"BUILD_DOCUMENTATION": "ON",
87+
"BUILD_COVERAGE": "OFF",
88+
"BUILD_CROSS_TARGET": "ON"
89+
}
90+
},
91+
{
92+
"name": "on-host",
93+
"inherits": [
94+
"common"
95+
],
8096
"displayName": "Host Build",
8197
"description": "Builds the project on the host machine, using the toolchain file",
8298
"cacheVariables": {
@@ -117,6 +133,22 @@
117133
"description": "Configuration for Cortex M7",
118134
"toolchainFile": "${sourceDir}/build-support/lib/cmake/bare-gcc-cortex-m7.cmake"
119135
},
136+
{
137+
"name": "cortex-m4-gcc-arm-none-eabi-ci",
138+
"hidden": false,
139+
"displayName": "cortex-m4 (ci)",
140+
"inherits": "on-target-ci",
141+
"description": "CI configuration for Cortex M4 without distcc",
142+
"toolchainFile": "${sourceDir}/build-support/lib/cmake/bare-gcc-cortex-m4.cmake"
143+
},
144+
{
145+
"name": "cortex-m7-gcc-arm-none-eabi-ci",
146+
"hidden": false,
147+
"displayName": "cortex-m7 (ci)",
148+
"inherits": "on-target-ci",
149+
"description": "CI configuration for Cortex M7 without distcc",
150+
"toolchainFile": "${sourceDir}/build-support/lib/cmake/bare-gcc-cortex-m7.cmake"
151+
},
120152
{
121153
"name": "native-gcc",
122154
"hidden": false,
@@ -163,6 +195,18 @@
163195
"targets": "all",
164196
"configurePreset": "cortex-m7-gcc-arm-none-eabi"
165197
},
198+
{
199+
"name": "build-cortex-m4-gcc-arm-none-eabi-ci",
200+
"inherits": "build-common",
201+
"targets": "all",
202+
"configurePreset": "cortex-m4-gcc-arm-none-eabi-ci"
203+
},
204+
{
205+
"name": "build-cortex-m7-gcc-arm-none-eabi-ci",
206+
"inherits": "build-common",
207+
"targets": "all",
208+
"configurePreset": "cortex-m7-gcc-arm-none-eabi-ci"
209+
},
166210
{
167211
"name": "build-native-gcc",
168212
"inherits": "build-common",
@@ -248,6 +292,32 @@
248292
}
249293
]
250294
},
295+
{
296+
"name": "on-target-cortex-m4-gcc-arm-none-eabi-ci",
297+
"steps": [
298+
{
299+
"type": "configure",
300+
"name": "cortex-m4-gcc-arm-none-eabi-ci"
301+
},
302+
{
303+
"type": "build",
304+
"name": "build-cortex-m4-gcc-arm-none-eabi-ci"
305+
}
306+
]
307+
},
308+
{
309+
"name": "on-target-cortex-m7-gcc-arm-none-eabi-ci",
310+
"steps": [
311+
{
312+
"type": "configure",
313+
"name": "cortex-m7-gcc-arm-none-eabi-ci"
314+
},
315+
{
316+
"type": "build",
317+
"name": "build-cortex-m7-gcc-arm-none-eabi-ci"
318+
}
319+
]
320+
},
251321
{
252322
"name": "on-host-native-gcc",
253323
"steps": [

build-support/share/cmake/embedded-superloop/embedded-superloop.cmake

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,34 @@ function(print_target_properties)
269269

270270
endfunction()
271271

272+
# Links each library in the list to TARGET using --whole-archive / --no-whole-archive so
273+
# that every object file in each archive is unconditionally included. This is required
274+
# when a library's inline code (e.g. inlined destructors that reference vtables) pulls in
275+
# symbols that live in sibling archives which the linker would otherwise skip.
276+
#
277+
# On linkers that support the CMake WHOLE_ARCHIVE link-library feature (CMake >= 3.24,
278+
# GNU ld, lld) the generator-expression form is used. On others the raw
279+
# -Wl,--whole-archive / -Wl,--no-whole-archive flags are emitted directly.
280+
#
281+
# Usage:
282+
# target_link_whole_libraries(<target> <lib1> [<lib2> ...])
283+
function(target_link_whole_libraries TARGET)
284+
if(CMAKE_CXX_LINK_LIBRARY_USING_WHOLE_ARCHIVE_SUPPORTED)
285+
foreach(lib IN LISTS ARGN)
286+
target_link_libraries(${TARGET} PRIVATE "$<LINK_LIBRARY:WHOLE_ARCHIVE,${lib}>")
287+
message(STATUS "Whole-archive linking ${TARGET} to ${lib}")
288+
endforeach()
289+
else()
290+
# Fallback: emit raw linker flags around each archive individually.
291+
foreach(lib IN LISTS ARGN)
292+
target_link_libraries(${TARGET} PRIVATE
293+
"-Wl,--whole-archive" "${lib}" "-Wl,--no-whole-archive"
294+
)
295+
message(STATUS "Whole-archive (raw flags) linking ${TARGET} to ${lib}")
296+
endforeach()
297+
endif()
298+
endfunction()
299+
272300
include(${CMAKE_CURRENT_LIST_DIR}/configuration.cmake)
273301
include(${CMAKE_CURRENT_LIST_DIR}/family.cmake)
274302
include(${CMAKE_CURRENT_LIST_DIR}/chip.cmake)

0 commit comments

Comments
 (0)