Add native Windows build of meshtasticd#11031
Conversation
📝 WalkthroughWalkthroughAdds a reusable Windows binary workflow and native-windows PlatformIO environment, with MSYS2 toolchain setup, static linking, artifact validation, and Windows-specific adaptations for sockets, MAC discovery, randomness, timekeeping, headers, and platform hooks. ChangesWindows native support
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant GitHubActions
participant MSYS2
participant PlatformIO
participant ArtifactStore
GitHubActions->>MSYS2: Install UCRT64 dependencies and argp
GitHubActions->>PlatformIO: Run native-windows build
PlatformIO->>GitHubActions: Produce meshtasticd.exe
GitHubActions->>ArtifactStore: Upload versioned artifact
Possibly related PRs
Suggested labels: Suggested reviewers: Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/build_windows_bin.yml:
- Around line 24-27: Update the actions/checkout step to explicitly set
persist-credentials to false in its with configuration, while preserving
recursive submodule checkout.
In `@src/main.cpp`:
- Around line 431-440: Shorten the comment above the non-Windows timedatectl
check to one or two lines, retaining only the essential reason for excluding
Windows while removing implementation details and follow-up context. Leave the
guarded command and RTCQualityNTP assignment unchanged.
In `@src/modules/AdminModule.h`:
- Line 44: Update the session timeout logic using session_time so it no longer
compares a future deadline against millis() / 1000; use the existing Throttle
mechanism or a rollover-safe unsigned elapsed-time subtraction, preserving the
300-second timeout behavior.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: ec8aa4b2-fa99-4857-b66c-4c2785f588bd
📒 Files selected for processing (13)
.github/workflows/build_windows_bin.ymlextra_scripts/windows_link_flags.pysrc/gps/RTC.cppsrc/main.cppsrc/mesh/HardwareRNG.cppsrc/modules/AdminModule.hsrc/mqtt/MQTT.cppsrc/platform/portduino/GpsdSerial.cppsrc/platform/portduino/PortduinoGlue.cppsrc/platform/portduino/WindowsMacAddr.cppsrc/power/PowerHAL.cppvariants/native/portduino.inivariants/native/portduino/platformio.ini
ecbeeed to
3dcf4dc
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
extra_scripts/windows_link_flags.py (1)
4-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winCondense the static-linking rationale.
This 14-line comment block is more detailed than necessary and includes brittle dependency/runtime claims. Keep only the reason this script appends linker flags; move historical or diagnostic detail to documentation if needed.
Suggested simplification
-# -# Static-link settings for [env:native-windows]. -# -# PlatformIO routes build_flags to the compile step, not the link step (SCons' -# ParseFlags sends an unrecognized flag like -static to CCFLAGS, where the linker -# never sees it), so it is appended to LINKFLAGS here. Same reason -# extra_scripts/wasm_link_flags.py exists for [env:native-wasm]. -# -# Static keeps meshtasticd.exe a single self-contained file needing no MSYS2 -# runtime, and removes a real failure mode: Windows resolves DLLs from System32 -# before PATH, so an unrelated libusb-1.0.dll installed there by another -# product's driver package hijacks a dynamically linked build and kills it at -# startup with STATUS_ENTRYPOINT_NOT_FOUND. MSYS2 ships static archives for every -# dependency this env uses, and openssl is not actually linked in. +# Static-link settings for [env:native-windows]; build_flags do not reliably +# place -static on the linker command, so append it explicitly here.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@extra_scripts/windows_link_flags.py` around lines 4 - 17, Condense the comment block above the Windows linker-flag configuration to only explain why this script appends static-link flags to LINKFLAGS instead of relying on build_flags, matching the purpose of extra_scripts/wasm_link_flags.py. Remove the detailed runtime, dependency, and historical failure-mode claims from this code comment.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@extra_scripts/windows_link_flags.py`:
- Around line 4-17: Condense the comment block above the Windows linker-flag
configuration to only explain why this script appends static-link flags to
LINKFLAGS instead of relying on build_flags, matching the purpose of
extra_scripts/wasm_link_flags.py. Remove the detailed runtime, dependency, and
historical failure-mode claims from this code comment.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: 47577c82-eb0d-480a-b64c-c725824f5cb0
📒 Files selected for processing (13)
.github/workflows/build_windows_bin.ymlextra_scripts/windows_link_flags.pysrc/gps/RTC.cppsrc/main.cppsrc/mesh/HardwareRNG.cppsrc/modules/AdminModule.hsrc/mqtt/MQTT.cppsrc/platform/portduino/GpsdSerial.cppsrc/platform/portduino/PortduinoGlue.cppsrc/platform/portduino/WindowsMacAddr.cppsrc/power/PowerHAL.cppvariants/native/portduino.inivariants/native/portduino/platformio.ini
🚧 Files skipped from review as they are similar to previous changes (11)
- variants/native/portduino.ini
- src/gps/RTC.cpp
- variants/native/portduino/platformio.ini
- src/mesh/HardwareRNG.cpp
- src/mqtt/MQTT.cpp
- src/platform/portduino/WindowsMacAddr.cpp
- src/power/PowerHAL.cpp
- src/main.cpp
- src/platform/portduino/GpsdSerial.cpp
- src/platform/portduino/PortduinoGlue.cpp
- .github/workflows/build_windows_bin.yml
⚡ Try this PR in the Web FlasherNote Building this pull request… the flash button, badges and supported-board |
Add [env:native-windows], a headless Portduino build for Windows x86_64 via the
MSYS2 UCRT64 MinGW-w64 toolchain, running in SimRadio mode alongside the existing
Linux and macOS native targets. MSVC is not viable: platform-native's builder
calls env.Tool("gcc") and the firmware builds as gnu17/gnu++17.
Link statically. Windows resolves DLLs from System32 before PATH, so an unrelated
libusb-1.0.dll installed there by another product's driver package hijacks a
dynamically linked build and kills it at startup with STATUS_ENTRYPOINT_NOT_FOUND.
Static linking also leaves a single self-contained meshtasticd.exe needing no
MSYS2 runtime. -static cannot ride in build_flags, which PlatformIO routes only
to the compile step, so extra_scripts/windows_link_flags.py appends it to
LINKFLAGS, as wasm_link_flags.py does for native-wasm.
HardwareRNG.cpp: use BCryptGenRandom() where Linux uses getrandom() and Darwin
uses arc4random_buf(). Not std::random_device, whose libstdc++ Windows backend
reports entropy() == 0 and so promises no cryptographic source, and this buffer
seeds key material.
PortduinoGlue.cpp: derive the MAC from the host's primary adapter via
GetAdaptersAddresses(), standing in for the BlueZ path on Linux and the en0 path
on macOS. Without it the MAC stays all-zero, device_id is left unset and every
user has to pass --hwid. The call lives in WindowsMacAddr.cpp because
<iphlpapi.h> pulls in RPC/OLE headers that collide with the Arduino API. Also
guard the ioctl() include, which only the Linux hardware path uses.
GpsdSerial.cpp: port the gpsd TCP client to Winsock.
PowerHAL.cpp: define the defaults strongly on Windows. PE/COFF has no ELF-style
weak definitions; GNU as lowers __attribute__((weak)) to a COFF weak external
that the linker treats as undefined, so these would not link. Only nrf52 and
nrf54l15 override them.
RTC.cpp: copy timeval::tv_sec through a time_t before taking its address, as
time_t is 64-bit on Windows while long is 32-bit.
MQTT.cpp: take ntohl() from <winsock2.h>.
AdminModule.h: uint32_t for session_time, as uint is a glibc typedef.
main.cpp: skip the timedatectl shell-out, which is systemd-only and otherwise
prints "not recognized" on every boot.
Bump platform-native to 86c62ed for the Windows builder support, which pulls
framework-portduino 0fdf803.
Build with the MSYS2 UCRT64 toolchain; see the env comment for the argp
prerequisite, which is not packaged for MSYS2's mingw environments.
3dcf4dc to
fb9796f
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
extra_scripts/windows_link_flags.py (1)
5-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winKeep the explanatory comment within the project’s comment-length convention.
Condense this three-line explanation to one or two lines; the implementation itself is already clear from the referenced environment and linker flags.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@extra_scripts/windows_link_flags.py` around lines 5 - 7, Condense the explanatory comment in extra_scripts/windows_link_flags.py to one or two lines while preserving that PlatformIO applies build_flags only during compilation, so the native-windows static link must be added to LINKFLAGS, as in the wasm counterpart.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@extra_scripts/windows_link_flags.py`:
- Around line 5-7: Condense the explanatory comment in
extra_scripts/windows_link_flags.py to one or two lines while preserving that
PlatformIO applies build_flags only during compilation, so the native-windows
static link must be added to LINKFLAGS, as in the wasm counterpart.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 3284aff8-0c92-4901-bf26-ea124cd73848
📒 Files selected for processing (13)
.github/workflows/build_windows_bin.ymlextra_scripts/windows_link_flags.pysrc/gps/RTC.cppsrc/main.cppsrc/mesh/HardwareRNG.cppsrc/modules/AdminModule.hsrc/mqtt/MQTT.cppsrc/platform/portduino/GpsdSerial.cppsrc/platform/portduino/PortduinoGlue.cppsrc/platform/portduino/WindowsMacAddr.cppsrc/power/PowerHAL.cppvariants/native/portduino.inivariants/native/portduino/platformio.ini
🚧 Files skipped from review as they are similar to previous changes (8)
- src/mqtt/MQTT.cpp
- src/modules/AdminModule.h
- src/mesh/HardwareRNG.cpp
- src/gps/RTC.cpp
- .github/workflows/build_windows_bin.yml
- src/platform/portduino/GpsdSerial.cpp
- src/platform/portduino/WindowsMacAddr.cpp
- src/platform/portduino/PortduinoGlue.cpp
| #if ARCH_PORTDUINO | ||
| RTCQuality ourQuality = RTCQualityDevice; | ||
|
|
||
| #ifndef _WIN32 |
There was a problem hiding this comment.
This should be switch to target linux specifically. Unsupported on MacOS, WASM, or Windows.
There was a problem hiding this comment.
Nothing calls this workflow yet. We should put it into the main CI (or add a workflow_dispatch)
|
I commented on Discord, but seems a good idea to capture the thought here: It may be useful to target serialHal as the first real hardware target, as that should be an easier lift than ch341. |
Adds
[env:native-windows], a headless Portduino build of meshtasticd forWindows x86_64 via the MSYS2 UCRT64 MinGW-w64 toolchain, running in SimRadio mode
alongside the existing Linux and macOS native targets.
Final part of a four-repo cascade. The other three are merged and pinned here:
03e866d0fdf80386c62ed, pinned bythe
variants/native/portduino.inibump in this PRMSVC is not a viable route: platform-native's builder calls
env.Tool("gcc")andthe firmware builds as
gnu17/gnu++17with GNU extensions throughout.Scope
Headless SimRadio, matching how
native-macosstarted. Not included: UDPmulticast (framework
AsyncUDP.cppis BSD sockets, soHAS_UDP_MULTICASTstaysunset exactly as on macOS), real UART (no termios on Windows), and CH341 LoRa
hardware. libusb is still linked, so CH341 remains reachable later; it would need
a WinUSB/Zadig driver bind.
Static linking
extra_scripts/windows_link_flags.pyappends-staticto LINKFLAGS, becausePlatformIO routes
build_flagsonly to the compile step (SCons' ParseFlags sends-staticto CCFLAGS, where the linker never sees it). Same reasonwasm_link_flags.pyexists fornative-wasm.This is not only convenience. Windows resolves DLLs from System32 before PATH,
so an unrelated
libusb-1.0.dlldropped there by another product's driver packagehijacks a dynamically linked build and kills it at startup with
STATUS_ENTRYPOINT_NOT_FOUND. This was hit in practice during development. Staticlinking removes the failure mode and yields a single self-contained
meshtasticd.exeneeding no MSYS2 runtime.Notes for review
HardwareRNG.cppusesBCryptGenRandom(), where Linux usesgetrandom()andDarwin
arc4random_buf(). Deliberately not falling through to the existingstd::random_devicefallback: its libstdc++ Windows backend reportsentropy() == 0, i.e. it promises no cryptographic source, and this buffer seedskey material. (Measured: it does vary per run, but the guarantee is absent.)
PortduinoGlue.cppderives the MAC from the host's primary adapter viaGetAdaptersAddresses(), standing in for BlueZ on Linux and en0 on macOS.Without it the MAC stays all-zero,
device_idis left unset, and every user hasto pass
--hwid. Verified stable: two independent fresh VFS dirs both yieldnodenum
0x4d949398. The call lives in a separateWindowsMacAddr.cppbecause<iphlpapi.h>pulls in the RPC/OLE chain, whosebooleanandMSGtypedefscollide with the Arduino API, which is why this env builds with
-DNOUSER -DWIN32_LEAN_AND_MEAN -DNOGDIin the first place.PowerHAL.cppdefines its defaults strongly on Windows. PE/COFF has noELF-style weak definitions: GNU as lowers
__attribute__((weak))to a COFF weakexternal that the linker treats as an undefined reference, so the defaults would
not link. Only nrf52 and nrf54l15 override them, and neither targets Windows.
Two include blocks are wrapped in
clang-format off.bcrypt.hneedsLONG/ULONGfromwindows.hand does not include it itself, andwinsock2.hmust precede
windows.hor the winsock v1 header collides. Alphabetical sortingbreaks both, and the resulting failures are Windows-only.
The remaining firmware changes are one-liners:
RTC.cppcopiestimeval::tv_secthrough a
time_t(64-bit on Windows, wherelongis 32-bit);MQTT.cpptakesntohl()from<winsock2.h>;AdminModule.husesuint32_tforsession_time(
uintis a glibc typedef);main.cppskips the systemd-onlytimedatectlshell-out, which otherwise prints "not recognized" on every boot.
Prerequisite
argp is not packaged for MSYS2's mingw environments. Only
msys/libargpexists,which links
msys-2.0.dlland so cannot be used for a native binary, yetframework-portduino's
Arduino.hincludes<argp.h>and itsmain.cppcallsargp_parse(). It has to be built once from source, the same dependency macOSmeets with
brew install argp-standalone. Steps are in the env comment and in theCI workflow.
Testing
Built and run on Windows 11 with MSYS2 UCRT64 (gcc 16.1.0) via plain
pio run -e native-windowswith no local overrides, so the platform-native pin,framework-portduino and its WiFi submodule were all resolved from GitHub exactly
as a clean consumer gets them.
objdump -pshows no third-party DLLdependencies, only Windows system DLLs.
sim init successandAPI server listen on TCP port 4403..github/workflows/build_windows_bin.ymlmirrors the macOS job, including aguard that fails if the static link ever regresses to needing third-party DLLs.
It is
continue-on-errorwhile Windows support is in development, as macOS is.The workflow itself has not run yet and is untested.
Linux and macOS targets are untouched by this PR.
Summary by CodeRabbit
meshtasticd.exeartifact, including basic DLL dependency checks and a--versionsmoke test.session_timeto a 32-bit type.