Skip to content

Add native Windows build of meshtasticd#11031

Open
caveman99 wants to merge 1 commit into
developfrom
windows-support
Open

Add native Windows build of meshtasticd#11031
caveman99 wants to merge 1 commit into
developfrom
windows-support

Conversation

@caveman99

@caveman99 caveman99 commented Jul 16, 2026

Copy link
Copy Markdown
Member

Adds [env:native-windows], a headless Portduino build of meshtasticd for
Windows 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:

MSVC is not a viable route: platform-native's builder calls env.Tool("gcc") and
the firmware builds as gnu17/gnu++17 with GNU extensions throughout.

Scope

Headless SimRadio, matching how native-macos started. Not included: UDP
multicast (framework AsyncUDP.cpp is BSD sockets, so HAS_UDP_MULTICAST stays
unset 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.py appends -static to LINKFLAGS, because
PlatformIO routes build_flags only to the compile step (SCons' ParseFlags sends
-static to CCFLAGS, where the linker never sees it). Same reason
wasm_link_flags.py exists for native-wasm.

This is not only convenience. Windows resolves DLLs from System32 before PATH,
so an unrelated libusb-1.0.dll dropped there by another product's driver package
hijacks a dynamically linked build and kills it at startup with
STATUS_ENTRYPOINT_NOT_FOUND. This was hit in practice during development. Static
linking removes the failure mode and yields a single self-contained
meshtasticd.exe needing no MSYS2 runtime.

Notes for review

HardwareRNG.cpp uses BCryptGenRandom(), where Linux uses getrandom() and
Darwin arc4random_buf(). Deliberately not falling through to the existing
std::random_device fallback: its libstdc++ Windows backend reports
entropy() == 0, i.e. it promises no cryptographic source, and this buffer seeds
key material. (Measured: it does vary per run, but the guarantee is absent.)

PortduinoGlue.cpp derives the MAC from the host's primary adapter via
GetAdaptersAddresses(), standing in for BlueZ on Linux and en0 on macOS.
Without it the MAC stays all-zero, device_id is left unset, and every user has
to pass --hwid. Verified stable: two independent fresh VFS dirs both yield
nodenum 0x4d949398. The call lives in a separate WindowsMacAddr.cpp because
<iphlpapi.h> pulls in the RPC/OLE chain, whose boolean and MSG typedefs
collide with the Arduino API, which is why this env builds with -DNOUSER -DWIN32_LEAN_AND_MEAN -DNOGDI in the first place.

PowerHAL.cpp defines its 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 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.h needs
LONG/ULONG from windows.h and does not include it itself, and winsock2.h
must precede windows.h or the winsock v1 header collides. Alphabetical sorting
breaks both, and the resulting failures are Windows-only.

The remaining firmware changes are one-liners: RTC.cpp copies timeval::tv_sec
through a time_t (64-bit on Windows, where long is 32-bit); MQTT.cpp takes
ntohl() from <winsock2.h>; AdminModule.h uses uint32_t for session_time
(uint is a glibc typedef); main.cpp skips the systemd-only timedatectl
shell-out, which otherwise prints "not recognized" on every boot.

Prerequisite

argp is not packaged for MSYS2's mingw environments. Only msys/libargp exists,
which links msys-2.0.dll and so cannot be used for a native binary, yet
framework-portduino's Arduino.h includes <argp.h> and its main.cpp calls
argp_parse(). It has to be built once from source, the same dependency macOS
meets with brew install argp-standalone. Steps are in the env comment and in the
CI workflow.

Testing

Built and run on Windows 11 with MSYS2 UCRT64 (gcc 16.1.0) via plain
pio run -e native-windows with 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.

  • Produces a static PE32+ x86-64 binary; objdump -p shows no third-party DLL
    dependencies, only Windows system DLLs.
  • Runs on a clean PATH with no MSYS2 present.
  • Boots headless to sim init success and API server listen on TCP port 4403.
  • Round-trips all 5 protobuf pref files across restarts.

.github/workflows/build_windows_bin.yml mirrors the macOS job, including a
guard that fails if the static link ever regresses to needing third-party DLLs.
It is continue-on-error while 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

  • New Features
    • Added a reusable Windows CI workflow that builds a versioned, headless meshtasticd.exe artifact, including basic DLL dependency checks and a --version smoke test.
    • Introduced a Windows-native/headless build environment with optional static-linking support.
  • Bug Fixes
    • Improved Windows RTC time-quality probing and corrected RTC time conversion for affected device types.
    • Enhanced Windows networking/telemetry reliability: MQTT byte order, gpsd socket handling, Windows MAC address detection, and Windows-based entropy generation.
    • Updated Windows power-hook linkage behavior and standardized session_time to a 32-bit type.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds 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.

Changes

Windows native support

Layer / File(s) Summary
Windows build pipeline and environment
.github/workflows/build_windows_bin.yml, variants/native/..., extra_scripts/windows_link_flags.py
Adds the reusable Windows build workflow, MSYS2/UCRT64 dependencies, argp build, native-windows PlatformIO environment, static-link flags, binary checks, smoke test, and artifact upload.
Windows platform API adaptations
src/mesh/HardwareRNG.cpp, src/mqtt/MQTT.cpp, src/power/PowerHAL.cpp, src/modules/AdminModule.h
Adds Windows cryptographic randomness and byte-order headers, adjusts default platform hook linkage, and uses uint32_t for session_time.
Portduino networking and host identity
src/platform/portduino/GpsdSerial.cpp, src/platform/portduino/PortduinoGlue.cpp, src/platform/portduino/WindowsMacAddr.cpp
Adds cross-platform socket helpers, Windows MAC-address discovery, Windows header guards, and narrow-string filesystem path handling.
Windows timekeeping behavior
src/gps/RTC.cpp, src/main.cpp
Uses local time_t values for RTC conversions and excludes timedatectl RTC quality detection on Windows.

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
Loading

Possibly related PRs

Suggested labels: enhancement

Suggested reviewers: thebentern, jp-bennett, vidplace7

Poem

A rabbit hops through Windows code,
With static links along the road.
Sockets chat and clocks align,
MAC addresses now shine.
“Build the binary!” cries the hare—
An artifact waits up there!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 21.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding a native Windows build of meshtasticd.
Description check ✅ Passed The description is detailed and mostly matches the template, though it omits the attestation checklist.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 4308487 and dc5e67b.

📒 Files selected for processing (13)
  • .github/workflows/build_windows_bin.yml
  • extra_scripts/windows_link_flags.py
  • src/gps/RTC.cpp
  • src/main.cpp
  • src/mesh/HardwareRNG.cpp
  • src/modules/AdminModule.h
  • src/mqtt/MQTT.cpp
  • src/platform/portduino/GpsdSerial.cpp
  • src/platform/portduino/PortduinoGlue.cpp
  • src/platform/portduino/WindowsMacAddr.cpp
  • src/power/PowerHAL.cpp
  • variants/native/portduino.ini
  • variants/native/portduino/platformio.ini

Comment thread .github/workflows/build_windows_bin.yml
Comment thread src/main.cpp
Comment thread src/modules/AdminModule.h
@caveman99 caveman99 force-pushed the windows-support branch 2 times, most recently from ecbeeed to 3dcf4dc Compare July 16, 2026 20:10

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
extra_scripts/windows_link_flags.py (1)

4-17: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Condense 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

📥 Commits

Reviewing files that changed from the base of the PR and between ecbeeed and 3dcf4dc.

📒 Files selected for processing (13)
  • .github/workflows/build_windows_bin.yml
  • extra_scripts/windows_link_flags.py
  • src/gps/RTC.cpp
  • src/main.cpp
  • src/mesh/HardwareRNG.cpp
  • src/modules/AdminModule.h
  • src/mqtt/MQTT.cpp
  • src/platform/portduino/GpsdSerial.cpp
  • src/platform/portduino/PortduinoGlue.cpp
  • src/platform/portduino/WindowsMacAddr.cpp
  • src/power/PowerHAL.cpp
  • variants/native/portduino.ini
  • variants/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

@github-actions

Copy link
Copy Markdown
Contributor

⚡ Try this PR in the Web Flasher

Note

Building this pull request… the flash button, badges and supported-board
list will appear here automatically once CI finishes.

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
extra_scripts/windows_link_flags.py (1)

5-7: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Keep 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

📥 Commits

Reviewing files that changed from the base of the PR and between 3dcf4dc and fb9796f.

📒 Files selected for processing (13)
  • .github/workflows/build_windows_bin.yml
  • extra_scripts/windows_link_flags.py
  • src/gps/RTC.cpp
  • src/main.cpp
  • src/mesh/HardwareRNG.cpp
  • src/modules/AdminModule.h
  • src/mqtt/MQTT.cpp
  • src/platform/portduino/GpsdSerial.cpp
  • src/platform/portduino/PortduinoGlue.cpp
  • src/platform/portduino/WindowsMacAddr.cpp
  • src/power/PowerHAL.cpp
  • variants/native/portduino.ini
  • variants/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

Comment thread src/main.cpp
#if ARCH_PORTDUINO
RTCQuality ourQuality = RTCQualityDevice;

#ifndef _WIN32

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be switch to target linux specifically. Unsupported on MacOS, WASM, or Windows.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nothing calls this workflow yet. We should put it into the main CI (or add a workflow_dispatch)

@jp-bennett

Copy link
Copy Markdown
Collaborator

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants