From a393b287d57147d7983edfc8464db98d252d65a3 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Fri, 3 Apr 2026 08:31:15 -0400 Subject: [PATCH 1/6] fix: don't tag pre-release Docker images as latest Pre-release tags (e.g. v4.1.0-rc.1) should not overwrite the latest tag in ghcr. Only stable releases (no hyphen suffix) get latest. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/docker.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 02007116..e4e82bb6 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -46,9 +46,9 @@ jobs: with: images: ghcr.io/autonomy-logic/openplc-runtime tags: | - # latest tag for tag pushes or manual runs with release_tag - type=raw,value=latest,enable=${{ startsWith(github.ref, 'refs/tags/v') || inputs.release_tag != '' }} - # Version tag from git tag (e.g., v1.2.3 -> v1.2.3) + # latest tag only for stable releases (no pre-release suffix like -rc, -beta, etc.) + type=raw,value=latest,enable=${{ (startsWith(github.ref, 'refs/tags/v') && !contains(github.ref, '-')) || inputs.release_tag != '' }} + # Version tag from git tag (e.g., v1.2.3 -> v1.2.3, v1.2.3-rc.1 -> v1.2.3-rc.1) type=semver,pattern={{raw}} # Version tag for manual runs with release_tag type=raw,value=${{ inputs.release_tag }},enable=${{ inputs.release_tag != '' }} From f1a70e91e4d633db3653d1097f9d42e487970a6d Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Fri, 3 Apr 2026 09:15:00 -0400 Subject: [PATCH 2/6] fix: skip Windows installer build for pre-release tags Pre-release tags (e.g. v4.1.0-rc.1) should only build Docker images, not Windows installers. Manual workflow_dispatch still works. Co-Authored-By: Claude Opus 4.6 (1M context) --- .github/workflows/windows-installer.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/windows-installer.yml b/.github/workflows/windows-installer.yml index 6904c37c..1d6579a3 100644 --- a/.github/workflows/windows-installer.yml +++ b/.github/workflows/windows-installer.yml @@ -13,6 +13,8 @@ on: jobs: build-windows-installer: + # Skip pre-release tags (those containing a hyphen, e.g. v4.1.0-rc.1) + if: ${{ !contains(github.ref_name, '-') || github.event_name == 'workflow_dispatch' }} runs-on: windows-latest permissions: contents: write From 9d4e573f8735a348ef007f9b22b122fc2bb870ed Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Fri, 5 Jun 2026 18:39:06 -0400 Subject: [PATCH 3/6] fix(windows): guard Linux-only POSIX RT calls behind feature macros MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit v4.1.1's Windows installer build failed at `core/src/plc_app/image_tables.cpp:96` with: error: 'PTHREAD_PRIO_INHERIT' was not declared in this scope `PTHREAD_PRIO_INHERIT` is the POSIX priority-inheritance mutex protocol — optional in the standard. MSYS2/MinGW pthread on Windows doesn't ship it (`_POSIX_THREAD_PRIO_INHERIT` is undefined and `pthread_mutexattr_setprotocol` isn't declared). Priority inheritance is only meaningful on a real-time-scheduled system (SCHED_FIFO threads); Windows has no RT scheduler, so the protocol would be a no-op even if it linked. Same broader pattern applies to the CPU-affinity block in `plc_state_manager.cpp::plc_task_thread`: `cpu_set_t` / `CPU_ZERO` / `CPU_SETSIZE` / `CPU_SET` / `pthread_setaffinity_np` are Linux `` extensions, not part of standard POSIX, and MSYS2's pthread headers don't define them. Fix: guard both blocks with `#if !defined(__CYGWIN__) && !defined(__MSYS__)` (plus `_POSIX_THREAD_PRIO_INHERIT` for the PI call and `__linux__` for the CPU-affinity block). Windows degrades to a plain recursive mutex + default scheduling — consistent with the platform's lack of real-time support. The existing `pthread_setschedparam(SCHED_FIFO)` call in the same function is already runtime-graceful (logs a warning on EPERM and falls through), so it doesn't need a compile-time guard; MSYS2's pthread.h declares the symbol but the kernel rejects the request at runtime, which is the behaviour we already handle. Reference: the same `#if !defined(__CYGWIN__) && !defined(__MSYS__)` pattern already exists in `core/src/plc_app/utils/utils.c` (gating `` / `` / `` includes via the local `HAS_REALTIME_FEATURES` macro), so this matches an established in-tree convention rather than introducing a new one. Co-Authored-By: Claude Opus 4.7 (1M context) --- core/src/plc_app/image_tables.cpp | 9 +++++++++ core/src/plc_app/plc_state_manager.cpp | 11 +++++++++++ 2 files changed, 20 insertions(+) diff --git a/core/src/plc_app/image_tables.cpp b/core/src/plc_app/image_tables.cpp index de419339..d2805d84 100644 --- a/core/src/plc_app/image_tables.cpp +++ b/core/src/plc_app/image_tables.cpp @@ -93,7 +93,16 @@ namespace { { pthread_mutexattr_t attr; if (pthread_mutexattr_init(&attr) != 0) return -1; + // Priority inheritance is a POSIX optional feature. MSYS2/Cygwin + // pthread on Windows doesn't ship it (PTHREAD_PRIO_INHERIT is + // undefined, pthread_mutexattr_setprotocol is unavailable). + // Windows has no real-time scheduling anyway, so the PI protocol + // would be a no-op even if it linked — fall back to a plain + // recursive mutex. +#if !defined(__CYGWIN__) && !defined(__MSYS__) && \ + defined(_POSIX_THREAD_PRIO_INHERIT) && _POSIX_THREAD_PRIO_INHERIT > 0 pthread_mutexattr_setprotocol(&attr, PTHREAD_PRIO_INHERIT); +#endif pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE); int rc = pthread_mutex_init(m, &attr); pthread_mutexattr_destroy(&attr); diff --git a/core/src/plc_app/plc_state_manager.cpp b/core/src/plc_app/plc_state_manager.cpp index fce8eb3f..c77116a5 100644 --- a/core/src/plc_app/plc_state_manager.cpp +++ b/core/src/plc_app/plc_state_manager.cpp @@ -140,6 +140,13 @@ static void *plc_task_thread(void *arg) if (ctx->cpu_affinity_mask != 0) { + // CPU affinity uses `cpu_set_t` / `CPU_ZERO` / `CPU_SETSIZE` / + // `pthread_setaffinity_np` — all Linux extensions to ``, + // not present on MSYS2/Cygwin/Windows. Skip silently when + // building for a non-Linux host: Windows doesn't expose + // SCHED_FIFO either, so any "pin to CPU" guarantee would already + // be unachievable there. +#if !defined(__CYGWIN__) && !defined(__MSYS__) && defined(__linux__) cpu_set_t cs; CPU_ZERO(&cs); for (int cpu = 0; cpu < 64 && cpu < CPU_SETSIZE; ++cpu) @@ -151,6 +158,10 @@ static void *plc_task_thread(void *arg) log_warn("[task %s] pthread_setaffinity_np failed: %s", ctx->name, strerror(errno)); } +#else + log_info("[task %s] CPU affinity requested but unsupported on this platform", + ctx->name); +#endif } if (sigsetjmp(ctx->crash_jmp, 1) != 0) From 8c0248857aa89d546c28438a85129f4aab35008c Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Fri, 5 Jun 2026 18:45:27 -0400 Subject: [PATCH 4/6] fix(windows): skip Linux-only EtherCAT plugin on MSYS2 builds MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After fixing the POSIX PI-mutex / CPU-affinity guards in image_tables.cpp + plc_state_manager.cpp, the Windows installer build progressed past the runtime and stopped on the EtherCAT plugin instead: /usr/include/w32api/winsock2.h:1031:34: error: conflicting types for 'select'; have 'int(int, fd_set *, fd_set *, fd_set *, const TIMEVAL *)' /usr/include/w32api/winsock2.h:1040:34: error: conflicting types for 'gethostname'; have 'int(char *, int)' Root cause: the vendored SOEM (Simple Open EtherCAT Master) ships its own Windows port under `core/src/drivers/plugins/native/ethercat/libs/soem/oshw/win32/wpcap/` that pulls in WinPcap headers, which collide with MSYS2's w32api `winsock2.h` (duplicate `select` / `gethostname` declarations). Even if those headers compiled cleanly the EtherCAT plugin itself relies on SCHED_FIFO + PREEMPT_RT scheduling that Windows doesn't provide — it's fundamentally Linux-only by design. No point trying to ship a non-functional Windows EtherCAT plugin. This patch teaches `build_native_plugins()` in install.sh to skip plugins listed in a `linux_only_plugins=("ethercat")` array when running under MSYS2. Linux/macOS builds are unchanged; Windows gets a clean build that excludes the plugin entirely and logs "Skipping Linux-only plugin on MSYS2/Windows: ethercat". The array makes future Linux-only plugins (eg. xenomai, custom PREEMPT_RT-dependent drivers) trivial to add. Co-Authored-By: Claude Opus 4.7 (1M context) --- install.sh | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/install.sh b/install.sh index f97fc861..1fcb4f87 100755 --- a/install.sh +++ b/install.sh @@ -466,6 +466,17 @@ build_native_plugins() { local plugins_built=0 local plugins_failed=0 + # Plugins that require Linux-only kernel features (SCHED_FIFO, + # PREEMPT_RT, raw sockets via libpcap with kernel bypass) — skip + # them entirely on MSYS2/Windows. The vendored SOEM EtherCAT + # master in particular ships its own Windows port (under + # `oshw/win32/wpcap/`) that conflicts with MSYS2's w32api + # `winsock2.h` (duplicate definitions of `select`, `gethostname`, + # etc.), and even if those headers compiled cleanly the runtime + # behaviour relies on PREEMPT_RT scheduling that Windows doesn't + # provide. These plugins are Linux-only by design. + local linux_only_plugins=("ethercat") + for plugin_dir in "$native_plugins_dir"/*/; do # Skip if not a directory [ -d "$plugin_dir" ] || continue @@ -478,6 +489,21 @@ build_native_plugins() { continue fi + # Skip Linux-only plugins on MSYS2/Windows + if is_msys2; then + local is_linux_only=0 + for skip in "${linux_only_plugins[@]}"; do + if [ "$plugin_name" = "$skip" ]; then + is_linux_only=1 + break + fi + done + if [ "$is_linux_only" = "1" ]; then + log_info "Skipping Linux-only plugin on MSYS2/Windows: $plugin_name" + continue + fi + fi + plugins_found=$((plugins_found + 1)) log_info "Found native plugin: $plugin_name" From 11204c5ee3c7cd1f86955353e5f6690ee29564ad Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Fri, 5 Jun 2026 18:50:08 -0400 Subject: [PATCH 5/6] Revert "fix(windows): skip Linux-only EtherCAT plugin on MSYS2 builds" This reverts commit 8c0248857aa89d546c28438a85129f4aab35008c. --- install.sh | 26 -------------------------- 1 file changed, 26 deletions(-) diff --git a/install.sh b/install.sh index 1fcb4f87..f97fc861 100755 --- a/install.sh +++ b/install.sh @@ -466,17 +466,6 @@ build_native_plugins() { local plugins_built=0 local plugins_failed=0 - # Plugins that require Linux-only kernel features (SCHED_FIFO, - # PREEMPT_RT, raw sockets via libpcap with kernel bypass) — skip - # them entirely on MSYS2/Windows. The vendored SOEM EtherCAT - # master in particular ships its own Windows port (under - # `oshw/win32/wpcap/`) that conflicts with MSYS2's w32api - # `winsock2.h` (duplicate definitions of `select`, `gethostname`, - # etc.), and even if those headers compiled cleanly the runtime - # behaviour relies on PREEMPT_RT scheduling that Windows doesn't - # provide. These plugins are Linux-only by design. - local linux_only_plugins=("ethercat") - for plugin_dir in "$native_plugins_dir"/*/; do # Skip if not a directory [ -d "$plugin_dir" ] || continue @@ -489,21 +478,6 @@ build_native_plugins() { continue fi - # Skip Linux-only plugins on MSYS2/Windows - if is_msys2; then - local is_linux_only=0 - for skip in "${linux_only_plugins[@]}"; do - if [ "$plugin_name" = "$skip" ]; then - is_linux_only=1 - break - fi - done - if [ "$is_linux_only" = "1" ]; then - log_info "Skipping Linux-only plugin on MSYS2/Windows: $plugin_name" - continue - fi - fi - plugins_found=$((plugins_found + 1)) log_info "Found native plugin: $plugin_name" From f8ca30351929e4449472cb7511840595441fc2c8 Mon Sep 17 00:00:00 2001 From: Thiago Alves Date: Fri, 5 Jun 2026 19:02:49 -0400 Subject: [PATCH 6/6] fix(windows): define __USE_W32_SOCKETS so SOEM compiles on MSYS2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The vendored SOEM library and its wpcap include path were already wired up for MSYS2 in the EtherCAT CMakeLists.txt (auto-generates a `MSYS.cmake` for SOEM that defines `WIN32` and adds the wpcap include directory). SOEM's own .c files compile fine — but `ethercat_plugin.c` and its siblings fail with: error: conflicting types for 'select'; have 'int(int, fd_set *, fd_set *, fd_set *, const TIMEVAL *)' note: previous declaration of 'select' with type 'int(int, fd_set *, fd_set *, fd_set *, struct timeval *)' Root cause: `ethercat_plugin.c` includes both `` (POSIX chain → `sys/select.h` → POSIX `select` with `struct timeval *`) AND `soem/soem.h` (→ `nicdrv.h` win32 → `pcap.h` → `pcap-stdinc.h` → `winsock2.h` → Win32 `select` with `const TIMEVAL *`). MSYS2's two libc's disagree on the signature so we get a redeclaration error. Same story for `gethostname` (`size_t` vs `int`). MSYS2's `sys/select.h` is explicit about this: /* We don't define fd_set and friends if we are compiling POSIX ... (defined by __USE_W32_SOCKETS) the W32api winsock[2].h header which it must not call the Cygwin select function. */ # if !(defined (_WINSOCK_H) || defined (_WINSOCKAPI_) || \ defined (__USE_W32_SOCKETS)) Same guard exists in `sys/unistd.h` around `gethostname`. Fix: add `__USE_W32_SOCKETS` to SOEM's `target_compile_definitions (PUBLIC ...)` block. The PUBLIC scope propagates the define to every target linking against soem — including `ethercat_plugin` — so the POSIX chain's `select` / `gethostname` decls get suppressed in those TUs and the Win32 wpcap-supplied decls win without conflict. No effect on Linux builds (the whole block is gated by `if(CMAKE_SYSTEM_NAME STREQUAL \"MSYS\" OR ... \"CYGWIN\")`). Verified by reproducing the conflict in a Windows VM with the same MSYS2 toolchain (gcc 15.2.0, w32api 14.0.0) and watching the EtherCAT plugin build go from \"conflicting types for 'select'\" to clean: \`libethercat_plugin.so\` artefact produced. Co-Authored-By: Claude Opus 4.7 (1M context) --- .../drivers/plugins/native/ethercat/CMakeLists.txt | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/core/src/drivers/plugins/native/ethercat/CMakeLists.txt b/core/src/drivers/plugins/native/ethercat/CMakeLists.txt index 40e5d57f..5126f2b2 100644 --- a/core/src/drivers/plugins/native/ethercat/CMakeLists.txt +++ b/core/src/drivers/plugins/native/ethercat/CMakeLists.txt @@ -60,8 +60,21 @@ target_compile_options(soem PUBLIC\n\ # HAVE_U_INT*_T tells bundled bittypes.h to skip its typedefs --\n\ # MSYS2 POSIX headers (sys/types.h) already provide these types and\n\ # use different underlying types on LP64 (long vs long long for 64-bit).\n\ +#\n\ +# __USE_W32_SOCKETS tells MSYS2 / Cygwin's POSIX headers\n\ +# (\`sys/select.h\`, \`sys/unistd.h\`) NOT to declare their own \`select\`\n\ +# and \`gethostname\` — the Win32 \`winsock2.h\` declarations (which the\n\ +# bundled wpcap headers pull in transitively) are the ones we want\n\ +# to use. Without this, any TU that includes both a POSIX header\n\ +# (e.g. \`\` in ethercat_plugin.c, which transitively pulls\n\ +# in sys/select.h) AND a SOEM/wpcap header (which pulls in\n\ +# winsock2.h) gets a redeclaration error because the two libcs\n\ +# disagree on the signature ( \`struct timeval *\` vs \`const TIMEVAL *\`,\n\ +# \`size_t\` vs \`int\`). Marking the macro PUBLIC propagates it to\n\ +# every target linking against soem (notably ethercat_plugin).\n\ target_compile_definitions(soem PUBLIC\n\ WIN32\n\ + __USE_W32_SOCKETS\n\ HAVE_U_INT8_T\n\ HAVE_U_INT16_T\n\ HAVE_U_INT32_T\n\