ff577391f2
El SDK CI - dev / build-and-test (pull_request) Successful in 7m7s
The desktop soul (neuron/dist) compiles against the v1.0.0-20260501 release runtime. After #66 landed the engram natives (tokenized/ranked search, engram_prune_telemetry) and #79 the durable truncation fix into this runtime, two gaps remained before it could cross-compile the Windows brain: 1. Windows OS boundary: the release runtime had no Win32 path. Ported the same _WIN32-guarded shim the mainline runtime carries (#69): #ifdef _WIN32 -> el_platform_win.h (winsock/dlsym/popen + WSAStartup ctor), SOCKET fd guards and el_closesocket() at every socket site, CreateProcessA for exec_bg, the tm_zone/mingw guard, an el_setsockopt optval wrapper (GCC14), and curl-less libcurl stubs. Every change is _WIN32/HAVE_CURL-gated — the POSIX build is byte-identical (gcc -fsyntax-only clean; native behaviour unchanged). 2. Header exports: the release el_runtime.h omitted symbols the soul dist calls that are defined in this runtime's .c — the http_handler_fn/http_handler4_fn typedefs and el_arena_push/pop, engram_prune_telemetry, engram_get_node_by_label. Declaration-only, POSIX-neutral; fixes implicit-declaration/unknown-type errors under the C11 mingw build. Result: x86_64-w64-mingw32-gcc compiles el_runtime.c + all 48 soul modules clean; POSIX gcc -fsyntax-only clean. This is the Windows-port PR the runtime needed on main (the release-runtime counterpart to #69), landed via stage.
187 lines
8.4 KiB
C
187 lines
8.4 KiB
C
#ifndef EL_PLATFORM_WIN_H
|
|
#define EL_PLATFORM_WIN_H
|
|
/*
|
|
* el_platform_win.h — Windows OS-boundary shim for el_runtime.c.
|
|
*
|
|
* Branch: feat/windows-el-runtime. Included ONLY when _WIN32 is defined; the POSIX build is
|
|
* untouched. Goal: let el_runtime.c (a BSD-sockets / dlfcn / fork host) compile and link with
|
|
* mingw-w64 into a native neuron.exe, with no behavioural change to the Linux/macOS build.
|
|
*
|
|
* What it maps:
|
|
* - sockets : winsock2 (same call names: socket/bind/listen/accept/recv/send/setsockopt).
|
|
* Sockets close with closesocket() (see el_closesocket), and the stack must be
|
|
* started once with WSAStartup — done automatically via a load-time constructor.
|
|
* - dlsym : el_runtime.c uses dlsym(RTLD_DEFAULT, name) to resolve callback/tool symbols
|
|
* exported by the main module. Windows equivalent: GetProcAddress on the process
|
|
* module. Link the soul with -Wl,--export-all-symbols so the symbols are findable.
|
|
* - popen : mapped to _popen/_pclose.
|
|
* - threads : UNCHANGED. mingw-w64 ships winpthreads, so <pthread.h> + -lpthread just work.
|
|
*/
|
|
|
|
#ifndef WIN32_LEAN_AND_MEAN
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#endif
|
|
#include <winsock2.h>
|
|
#include <ws2tcpip.h>
|
|
#include <windows.h>
|
|
#include <io.h>
|
|
#include <process.h>
|
|
|
|
/* Portable headers mingw-w64 provides (verified present). */
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <strings.h> /* strcasecmp */
|
|
#include <ctype.h>
|
|
#include <math.h>
|
|
#include <time.h>
|
|
#include <sys/time.h> /* mingw-w64 provides gettimeofday here */
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <dirent.h>
|
|
#include <errno.h>
|
|
#include <pthread.h>
|
|
|
|
/* ── socket close ─────────────────────────────────────────────────────────── */
|
|
/* Winsock closes sockets with closesocket(), not close() (close() is for file fds). The POSIX
|
|
build defines the same helper as close() so the call sites are identical across platforms. */
|
|
static inline int el_closesocket(SOCKET s) { return closesocket(s); }
|
|
|
|
/* ── setsockopt optval type ───────────────────────────────────────────────── */
|
|
/* Winsock's setsockopt takes optval as (const char*); POSIX takes (const void*), so el_runtime.c
|
|
passes &int directly. GCC 14+ makes that an error under -Wincompatible-pointer-types. Wrap it so
|
|
the runtime's POSIX-style call sites compile unchanged (defined before the macro so the wrapper
|
|
itself resolves to the real winsock setsockopt). */
|
|
static inline int el_setsockopt(SOCKET s, int level, int optname, const void* optval, int optlen) {
|
|
return setsockopt(s, level, optname, (const char*)optval, optlen);
|
|
}
|
|
#define setsockopt(s, l, o, v, n) el_setsockopt((s), (l), (o), (v), (int)(n))
|
|
|
|
/* ── winsock init (once, at load) ─────────────────────────────────────────── */
|
|
static void el__win_net_init(void) {
|
|
static int inited = 0;
|
|
if (!inited) { WSADATA w; WSAStartup(MAKEWORD(2, 2), &w); inited = 1; }
|
|
}
|
|
__attribute__((constructor)) static void el__win_ctor(void) { el__win_net_init(); }
|
|
|
|
/* ── dlsym → GetProcAddress ───────────────────────────────────────────────── */
|
|
#ifndef RTLD_DEFAULT
|
|
#define RTLD_DEFAULT ((void*)0)
|
|
#endif
|
|
static inline void* el_win_dlsym(void* handle, const char* name) {
|
|
(void)handle;
|
|
return (void*)(uintptr_t)GetProcAddress(GetModuleHandleA(NULL), name);
|
|
}
|
|
#define dlsym(h, n) el_win_dlsym((h), (n))
|
|
|
|
/* ── popen / pclose ───────────────────────────────────────────────────────── */
|
|
#define popen _popen
|
|
#define pclose _pclose
|
|
|
|
/* ── misc POSIX → Win32 shims ─────────────────────────────────────────────── */
|
|
#include <direct.h> /* _mkdir */
|
|
#define mkdir(path, mode) _mkdir(path) /* POSIX mkdir(path,mode) → _mkdir(path) */
|
|
#define timegm _mkgmtime /* UTC tm → time_t */
|
|
|
|
/* setenv/unsetenv: not in the Windows CRT; map to _putenv_s / SetEnvironmentVariable. */
|
|
static inline int setenv(const char* name, const char* value, int overwrite) {
|
|
(void)overwrite;
|
|
return _putenv_s(name, value ? value : "");
|
|
}
|
|
static inline int unsetenv(const char* name) {
|
|
/* _putenv_s(name, "") sets VAR="" rather than removing it.
|
|
* SetEnvironmentVariableA(name, NULL) truly deletes it from the Win32
|
|
* env block; then we sync the CRT cache with _putenv("NAME="). */
|
|
SetEnvironmentVariableA(name, NULL);
|
|
size_t len = strlen(name);
|
|
char *buf = (char*)malloc(len + 2);
|
|
if (!buf) return -1;
|
|
memcpy(buf, name, len);
|
|
buf[len] = '=';
|
|
buf[len + 1] = '\0';
|
|
_putenv(buf);
|
|
free(buf);
|
|
return 0;
|
|
}
|
|
|
|
/* nanosleep — not available in MSVC/UCRT; approximate with Sleep(). */
|
|
static inline int el_nanosleep(const struct timespec *req, struct timespec *rem) {
|
|
(void)rem;
|
|
DWORD ms = (DWORD)((req->tv_sec * 1000ULL) + (req->tv_nsec / 1000000ULL));
|
|
Sleep(ms ? ms : 1);
|
|
return 0;
|
|
}
|
|
#define nanosleep(req, rem) el_nanosleep((req), (rem))
|
|
|
|
/* localtime_r/gmtime_r: Windows offers localtime_s/gmtime_s with reversed arg order. */
|
|
static inline struct tm* localtime_r(const time_t* t, struct tm* out) {
|
|
return localtime_s(out, t) == 0 ? out : (struct tm*)0;
|
|
}
|
|
static inline struct tm* gmtime_r(const time_t* t, struct tm* out) {
|
|
return gmtime_s(out, t) == 0 ? out : (struct tm*)0;
|
|
}
|
|
|
|
/* ── libcurl: degradable stubs for the curl-less Windows build ─────────────── */
|
|
/* The curl-less validation build (WITH_CURL=0) links no libcurl. el_runtime.c uses libcurl
|
|
* unconditionally for its HTTP client / LLM layer; these stubs let it compile and link so the
|
|
* runtime, HTTP *server*, graph and memory work natively on Windows. Live outbound HTTP/LLM calls
|
|
* degrade to a runtime error (curl_easy_perform returns an error) — matching the documented
|
|
* curl-less contract. When HAVE_CURL is defined (WITH_CURL=1) the real <curl/curl.h> is used and
|
|
* this whole block is compiled out. POSIX never sees this header, so the POSIX build is untouched. */
|
|
#ifndef HAVE_CURL
|
|
|
|
typedef void CURL;
|
|
typedef int CURLcode;
|
|
|
|
#define CURLE_OK 0
|
|
#define CURLE_HTTP_RETURNED_ERROR 22
|
|
#define CURL_ERROR_SIZE 256
|
|
|
|
/* Option ids: values are irrelevant to the no-op setopt below; kept distinct for readability. */
|
|
#define CURLOPT_URL 10002
|
|
#define CURLOPT_WRITEFUNCTION 20011
|
|
#define CURLOPT_WRITEDATA 10001
|
|
#define CURLOPT_POSTFIELDS 10015
|
|
#define CURLOPT_POSTFIELDSIZE 120
|
|
#define CURLOPT_POST 47
|
|
#define CURLOPT_HTTPHEADER 10023
|
|
#define CURLOPT_TIMEOUT_MS 155
|
|
#define CURLOPT_NOSIGNAL 99
|
|
#define CURLOPT_USERAGENT 10018
|
|
#define CURLOPT_FOLLOWLOCATION 52
|
|
#define CURLOPT_ERRORBUFFER 10010
|
|
#define CURLOPT_CUSTOMREQUEST 10036
|
|
#define CURLOPT_FAILONERROR 45
|
|
|
|
struct curl_slist { char* data; struct curl_slist* next; };
|
|
|
|
static inline struct curl_slist* curl_slist_append(struct curl_slist* list, const char* s) {
|
|
struct curl_slist* node = (struct curl_slist*)malloc(sizeof(struct curl_slist));
|
|
if (!node) return list;
|
|
node->data = s ? strdup(s) : NULL;
|
|
node->next = NULL;
|
|
if (!list) return node;
|
|
struct curl_slist* p = list;
|
|
while (p->next) p = p->next;
|
|
p->next = node;
|
|
return list;
|
|
}
|
|
static inline void curl_slist_free_all(struct curl_slist* list) {
|
|
while (list) { struct curl_slist* n = list->next; free(list->data); free(list); list = n; }
|
|
}
|
|
|
|
static inline CURL* curl_easy_init(void) { return (CURL*)malloc(1); }
|
|
static inline CURLcode curl_easy_setopt(CURL* h, int opt, ...) { (void)h; (void)opt; return CURLE_OK; }
|
|
static inline CURLcode curl_easy_perform(CURL* h) { (void)h; return 7 /* CURLE_COULDNT_CONNECT */; }
|
|
static inline void curl_easy_cleanup(CURL* h) { free(h); }
|
|
static inline const char* curl_easy_strerror(CURLcode c) {
|
|
(void)c; return "libcurl not built in (curl-less build)";
|
|
}
|
|
|
|
#endif /* !HAVE_CURL */
|
|
|
|
#endif /* EL_PLATFORM_WIN_H */
|