Community-maintained, best effort. The native Windows build is kept
working by its users, not by Spinel's maintainers: it may lag behind
master, and nothing in CI waits on it (the one check is a compile-only
cross-build of the compiler and the runtime with x86_64-w64-mingw32-gcc
on Linux, which does not block a push). WSL
is the dependable way to run Spinel on Windows: there it builds and runs as
the Linux toolchain. Everything Windows needs lives in lib/win32/ and
win32.mk, so that if it ever stands in the way of a POSIX change and is
not fixed promptly, a maintainer can remove it cheaply.
# in RubyInstaller's Devkit shell (`ridk enable`), or an MSYS2 UCRT64 shell
pacman -S --needed make diffutils curl tar git mingw-w64-ucrt-x86_64-gcc
make deps && make
bin/spinel app.rb # -> app.exe
./app.exeSpinel builds and runs on Windows as a native program: the compiler is
spinel.exe, the C it emits is compiled by MinGW-w64's gcc against the UCRT
-- the toolchain RubyInstaller's Devkit ships -- and the programs it builds
are ordinary Windows executables that need no DLL beyond the system's own
(the MinGW runtime is linked in statically). spinel.exe runs from
PowerShell or cmd.exe as well as from MSYS2: with no gcc on PATH it
uses the Devkit beside the ruby on PATH (RubyInstaller puts it there
only under ridk enable), then the installs RubyInstaller records in the
registry, then C:\msys64.
spin runs its build steps as POSIX shell command lines, so on Windows it
needs a POSIX sh: set SPINEL_SHELL to one, MSYS2's for instance
(export SPINEL_SHELL=$(cygpath -m /usr/bin/sh) in a Devkit or UCRT64
shell). Without it a shell command runs under cmd.exe, as CRuby's do.
The runtime is written against POSIX, and it stays that way: everything
Windows needs is in lib/win32/ and win32.mk, the arrangement lib/wasi/
is for wasm32-wasi.
lib/win32/holds a header for each POSIX header MinGW leaves out or answers differently (unistd.h,sys/mman.h,poll.h,spawn.h,ucontext.h,sys/socket.h,signal.h,dirent.h,sys/stat.h,time.h,stdio.h, ...), each#include_next-ing MinGW's own where it has one, and the code behind them over Win32:sp_win32.c(files, processes, signals,mmap, ...),sp_win32_net.c(sockets as file descriptors,poll),sp_win32_ctx.c(the Windows x64 context switch),sp_win32_crypt.c(DEScrypt(3)) andsp_win32_driver.c(the compiler driver's startup). The shim headers include neither<windows.h>nor<winsock2.h>; only those.cfiles do, so neither the runtime nor a generated program sees their macros.win32.mk, whichcommon.mkincludes when$(OS)isWindows_NT, sets the toolchain and thePLATFORM_variables the Makefile's rules take (-Ilib/win32, the shim's objects and libraries, the static link, the test marker below) -- empty on every other host -- and emptylib/win32/lib{c,dl,rt,util,crypt}.a, so a program's-lcor-lcryptfinds the C runtime and the shim that answer them here.- The driver includes
lib/win32/sp_win32_driver.hon Windows, which sets what it otherwise defaults to POSIX's: the compiler (gcc), the.exe, the flags every program gets, and a startup hook.
What lib/ itself carries for Windows is small and named:
- The coroutine switch (
lib/sp_fiber_ctx.h): Windows takes theucontextfallback, whichlib/win32/ucontext.hprovides -- its x64 ABI saves more registers, and a switch there also moves the stack bounds the thread's TEB holds. - Starting a process (
sp_process.c,sp_system.c,sp_cold.c): there is nofork, soProcess.spawn,Kernel#systemand backticks have aposix_spawnpath, which the shim implements overCreateProcessW; a POSIX build keeps itsforkpath and never compiles this one. - Path roots (
sp_file_path_root_leninsp_alloc.h):File.expand_path,Dir.glob,File.absolute_path?,File.dirnameand the tmpdir package takeC:/as a root as well as/;Pathname.root_strdoes the same. - One-liners: the CSPRNG reads
getentropy(as on WASI), which the shim answers fromBCryptGenRandom;File.birthtimereads the creation time;%agoes through MinGW-w64's C99 printf;RUBY_PLATFORMisx64-mingw-ucrt, CRuby's spelling, and RbConfig andGem.win_platform?answer for it.
- Paths are the ones CRuby on Windows answers:
Dir.pwdandFile.expand_pathgiveC:/Users/me/...with forward slashes, and a backslash is accepted as a separator. Names are UTF-8 throughout (the shim calls the wide Win32 API)./dev/nullmeansNUL, and/tmpis the system temporary directory (%TEMP%), as under MSYS2 and Cygwin. - Line endings: files are read and written as bytes, as on POSIX --
no CRLF translation,
"w"and"wb"alike. CRuby on Windows opens a file in text mode unless asked for binary, so a file it writes withputshas CRLF line ends (and is that many bytes longer). - Shell commands --
system("..."), backticks, a one-stringProcess.spawn-- run under%ComSpec%(cmd.exe), as CRuby's do on Windows. SettingSPINEL_SHELLto a POSIXshruns them under that instead; the test harness does, under MSYS2. A program named by its POSIX path (/bin/echo,/usr/bin/env) is looked up onPATHby its name. - Signals are delivered as POSIX delivers them: a process-directed one
(the console's Ctrl-C, Ctrl-Break or close, which are
INT,BREAKandHUP; aProcess.killfrom another thread) interrupts the main thread and runs its handler there -- through a special user APC on Windows 11 and Server 2022, and by suspending the thread and redirecting it where those are missing -- and asleepit interrupts returns early withEINTRunderneath, as on POSIX. A signal is blocked while its own handler runs.Process.killon a child ends it (TerminateProcess), and$?reads the signal it was sent;INT/BREAKreach a child started in its own process group as a console Ctrl-Break. A fault in the program -- an overflowed stack included -- is delivered to its handler as SIGSEGV, on the alternate signal stack, so a deep recursion is aSystemStackErrorhere too. - No
fork:Process.forkand a prefork server answerErrno::ENOSYS.execruns the program and exits with its status when it ends (Windows cannot replace a process image), as the MSVC runtime's_execdoes. - Permissions and owners: a file's mode is what Windows can say --
read-only or not, and executable for
.exe/.com/.bat/.cmd-- soFile.chmod(0600, f)followed byFile.stat(f).modeanswers0100644.chownsucceeds and changes nothing;uid/gidare 0;Etc.getpwnamknows the current user only. - Sockets are file descriptors as on POSIX (
fdopen,IO.select, non-blocking mode);UNIXSocketneeds Windows 10 1803 or later.SO_REUSEADDRis accepted and has POSIX's meaning (winsock's own would let a second server take a port in use, so it is not passed through). - Symlinks need Developer Mode or an elevated process, as for any Windows program.
make test-corpus runs the corpus as on Linux, from an MSYS2 shell. The
tests that assert what only a POSIX system answers -- a path under /proc
or /dev/fd, permission bits, FIFOs, /etc/passwd, Linux's O_ flags --
are listed in win32.mk (TEST_SKIP, each group with its reason) and are
not run there; the tests themselves carry nothing for Windows. CI does not
run the corpus on Windows; whoever changes lib/win32/ runs it, and adds a
test to the list when it asserts something only POSIX can answer.