feat: Windows (MinGW-w64) + macOS support + multi-platform prebuilts (4.2.0)#14
Conversation
…4.2.0) Adds Windows and macOS build support and ships one prebuilt binary per platform, selected at runtime by a platform-aware loader. abieos itself is unchanged (submodule still f7d5b45). Windows (MinGW-w64; MSVC unsupported and now fails fast): - cmake-js only emits the MSVC node.lib and an _MSC_VER-gated delay-load hook. The MinGW path instead generates a dlltool delay-import library and compiles src/win_delay_load_hook.cc, binding napi_* to the host process image. One binary works under node.exe AND bun.exe (Bun previously crashed loading a hard-bound import lib). - CMakeLists.txt: MSVC fail-fast; portable submodule EXISTS check (replaces Unix-only `git submodule status | grep`); CRLF-robust node-addon-api include resolution. Distribution (Model A — all-in-one + smart loader): - lib/abieos.ts resolves ./abieos-<platform>-<arch>.node with fallback to ./abieos.node (also written by a source build). - scripts/copy-module.mjs emits the platform-named binary + generic fallback, and handles single- and multi-config generator layouts. - Committed prebuilts: linux-x64, win32-x64. darwin-x64/darwin-arm64 are produced by the new macOS CI jobs and must be committed from the CI artifacts before npm publish (Mach-O cannot be cross-built here). CI: added build-windows (msys2/setup-msys2 MinGW-w64) and build-macos (macos-14 arm64 + macos-13 x64) jobs; Linux job uploads its artifact too. Verified locally on Windows: Node 24 full suite 55/55; Bun end-to-end JSON<->hex round-trip. macOS is validated by CI only. Version 4.1.1-f7d5b45 -> 4.2.0-f7d5b45. README + CHANGELOG updated.
There was a problem hiding this comment.
Code Review
This pull request adds support for Windows (via MinGW-w64) and macOS, implementing a multi-platform prebuilt distribution system. Key updates include a dynamic runtime loader for platform-specific binaries, a custom Windows delay-load hook for compatibility with various runtimes (Node, Bun, Electron), and portable CMake configuration. Reviewers identified that the loader's search paths should be expanded to find binaries in both development and production layouts, recommended removing an unused import in the module copy script, and suggested using a non-zero exit code when the native module is missing to prevent silent CI failures.
| const candidates = [ | ||
| `./abieos-${process.platform}-${process.arch}.node`, | ||
| './abieos.node', | ||
| ]; |
There was a problem hiding this comment.
The current loader only looks for the native binary in the same directory as the script. Since the package entry point is in dist/ but the binaries are copied to lib/ by copy-module.mjs, the published package will fail to find the addon. Adding ../lib/ to the search candidates will allow the loader to find the binary in both development and production layouts. This change should also be reflected in the generated files in dist/.
const candidates = [
`./abieos-${process.platform}-${process.arch}.node`,
'./abieos.node',
`../lib/abieos-${process.platform}-${process.arch}.node`,
'../lib/abieos.node',
];| import { copyFile } from "node:fs/promises"; | ||
| import { copyFile, mkdir, readdir } from "node:fs/promises"; | ||
| import { existsSync } from "node:fs"; | ||
| import { join, dirname } from "node:path"; |
|
|
||
| if (!source) { | ||
| console.log(`${MODULE_NAME} not found under build/. Skipping copy.`); | ||
| process.exit(0); |
There was a problem hiding this comment.
Pull request overview
Adds Windows (MinGW-w64) and macOS build support and switches the package to a multi-platform prebuilt distribution model. A platform-aware loader in lib/abieos.ts picks abieos-<platform>-<arch>.node at runtime (with abieos.node as fallback), CMake fails fast on MSVC with guidance, and the MinGW build synthesizes its own Node-API delay-import lib + delay-load hook so the same binary works under node.exe, bun.exe, electron.exe, etc. CI gains build-windows and build-macos (arm64 + x64) jobs; the version bumps from 4.1.1-f7d5b45 to 4.2.0-f7d5b45.
Changes:
- Platform-aware native loader +
scripts/copy-module.mjsrewrite that emits bothabieos-<platform>-<arch>.nodeand a genericabieos.node. - MinGW-w64 Windows support: MSVC guard in CMake,
dlltool-generated delay-import lib, and a newsrc/win_delay_load_hook.ccredirecting Node-API imports to the host process image. - CI jobs for Windows and macOS, prebuilt artifact uploads, and README/CHANGELOG updates documenting the new multi-platform story.
Reviewed changes
Copilot reviewed 8 out of 15 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| src/win_delay_load_hook.cc | New delay-load hook binding napi_* to the host executable on MinGW/MSVC builds. |
| scripts/copy-module.mjs | Locates the built .node across generator layouts and writes both platform-named and generic copies into lib/. |
| lib/abieos.ts | Adds loadNative() that tries the platform-specific binary first, then ./abieos.node, with an actionable error. |
| dist/abieos.ts, dist/abieos.js, dist/abieos.cjs | Regenerated bundle outputs reflecting the new loader. |
| CMakeLists.txt | MSVC fail-fast, portable submodule presence check, MinGW delay-import lib wiring, CRLF-safe node-addon-api include resolution. |
| package.json | Version bump and real build:win / build:mac commands invoking cmake-js + copy-module. |
| .github/workflows/build.yml | New build-windows (MSYS2/MinGW-w64) and build-macos (arm64+x64) jobs and Linux artifact upload. |
| README.md | Documents multi-platform support, Windows MinGW-w64 / macOS build instructions, and the prebuilt distribution. |
| CHANGELOG.md | 4.2.0-f7d5b45 entry describing Windows/macOS support and multi-platform prebuilts. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - Prebuilt binaries bundled: `linux-x64`, `win32-x64`, `darwin-x64`, | ||
| `darwin-arm64` (loaded automatically by platform/arch) |
| ### Added | ||
| - **Windows support** via MinGW-w64 (GCC). `abieos` cannot be built with MSVC (its C++ depends on libstdc++/libc++ standard-library semantics and GCC/Clang extensions MSVC's STL/compiler does not provide); `CMakeLists.txt` now fails fast with an actionable message if MSVC is detected. cmake-js only emits the MSVC-format `node.lib` and an `#ifdef _MSC_VER`-gated delay-load hook, so the MinGW build instead generates a *delay-import* library with `dlltool` and compiles its own delay-load hook (`src/win_delay_load_hook.cc`) that binds the Node-API symbols to the **host process image**. The single Windows binary therefore works under `node.exe`, `bun.exe`, `electron.exe`, etc. — not just a file literally named `node.exe`. Verified locally on Windows: full test suite **55/55 passing** under Node 24, and end-to-end JSON⇄hex round-trip under Bun (which previously crashed before the delay-load hook). | ||
| - **macOS build support** (Clang/libc++ via Xcode Command Line Tools) — wired and exercised by CI; not yet validated locally (no macOS dev environment). See Notes. | ||
| - **Multi-platform prebuilt distribution.** The package now bundles one prebuilt binary per platform — `abieos-<platform>-<arch>.node` (`linux-x64`, `win32-x64`, `darwin-x64`, `darwin-arm64`) — and a platform-aware loader (`lib/abieos.ts`) selects the correct binary at runtime, falling back to the generic `abieos.node` (also written by a source build). `npm i` is zero-build on supported platforms. |
| # node.lib and the MSVC-only /DELAYLOAD flag (g++ would treat | ||
| # "/DELAYLOAD:NODE.EXE" as an input file). | ||
| set(CMAKE_JS_LIB "${NODE_DELAYLIB};delayimp") | ||
| set(CMAKE_SHARED_LINKER_FLAGS "") |
| async function findRecursively(dir) { | ||
| let entries; | ||
| try { | ||
| entries = await readdir(dir, { withFileTypes: true }); | ||
| } catch { | ||
| return null; | ||
| } | ||
| for (const entry of entries) { | ||
| const full = join(dir, entry.name); | ||
| if (entry.isDirectory()) { | ||
| const found = await findRecursively(full); | ||
| if (found) return found; | ||
| } else if (entry.name === MODULE_NAME) { | ||
| return full; | ||
| } | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| let source = candidates.find((p) => existsSync(p)); | ||
| if (!source) { | ||
| source = await findRecursively("build"); | ||
| } |
Review comments (gemini-code-assist, copilot): - lib/abieos.ts: export resolveNative(req, platform, arch) and add the ../lib/* candidates so the binary resolves in both dist/ (published) and lib/ (source) layouts. - scripts/copy-module.mjs: drop the unused dirname import and the unbounded recursive scan (could pick a stale node_abieos.node from build/_deps/**); exit non-zero when no binary is found instead of silently succeeding. - CMakeLists.txt: strip only the MSVC /DELAYLOAD:* token from CMAKE_SHARED_LINKER_FLAGS instead of wiping the whole variable, so toolchain/preset/LDFLAGS flags survive on MinGW. Coverage: - resolveNative is now an exported pure function; test/loader.test.js exercises platform-specific hit, ./abieos.node fallback, ../lib fallback, the per-platform error mapping, and a non-Error throw. - Local c8: 100% stmts/branch/funcs/lines; 60/60 tests pass; tsc clean. Reverses the -2.7% Coveralls regression.
GitHub's macos-13 (Intel x64) runner queued >3h and is being retired. Build a single universal2 (arm64 + x86_64) .node on the reliable macos-14 runner instead and publish it under both darwin arch names. - CMakeLists.txt: ABIEOS_MAC_UNIVERSAL option (ON) forces CMAKE_OSX_ARCHITECTURES=arm64;x86_64 on Apple (FORCE overrides the single-arch value cmake-js injects); -DABIEOS_MAC_UNIVERSAL=OFF for a faster host-only dev build. - scripts/copy-module.mjs: on darwin emit both abieos-darwin-arm64.node and abieos-darwin-x64.node (same universal binary) + generic fallback. - build.yml: build-macos is now a single macos-14 job; verifies both lipo slices and that the two darwin files are identical; uploads each. - CHANGELOG: macOS documented as universal2; darwin provenance = CI.
- Add darwin-arm64 + darwin-x64 prebuilts (the same universal2 Mach-O, x86_64 + arm64) from the green build-macos CI artifacts of run 26008788451. Completes Model A: linux-x64, win32-x64, darwin-arm64, darwin-x64 all bundled; npm i is zero-build on every supported platform. - .npmignore: exclude `coverage/`. npm packs from .npmignore (not .gitignore, which already ignored it), so a local `test:coverage` run was leaking 22 lcov/tmp files into the tarball. npm pack now ships 16 files (was 36); no coverage artifacts.
Review feedback addressed + coverage restoredThanks @gemini-code-assist and @copilot-pull-request-reviewer. All comments resolved across gemini-code-assist
copilot-pull-request-reviewer
Coverage — the loader is now an exported pure Also: GitHub's Intel |
|
Thanks for the comprehensive update, @igorls. The changes to the loader, the explicit handling of the build artifacts, and the improved CI/CD pipeline look excellent. Restoring 100% test coverage and ensuring non-zero exit codes for missing modules are great improvements for maintainability and CI reliability. This looks ready for merge. |
Summary
Adds Windows (MinGW-w64) and macOS build support and switches to
multi-platform prebuilt distribution: the package bundles one prebuilt
binary per platform and a platform-aware loader picks the right one at
runtime.
abieositself is unchanged (submodule stillf7d5b45); version4.1.1-f7d5b45→4.2.0-f7d5b45.Windows (MinGW-w64)
abieoscannot build with MSVC (its C++ needs libstdc++/libc++ semantics andGCC/Clang extensions MSVC's STL/compiler lack) —
CMakeLists.txtnow failsfast on MSVC with guidance.
cmake-js only emits the MSVC
node.liband an#ifdef _MSC_VER-gateddelay-load hook. The MinGW path instead:
.defwithdlltool;src/win_delay_load_hook.cc) that bindsnapi_*to the host process image.So the single Windows binary works under
node.exe,bun.exe,electron.exe— not a file literally named
node.exe. (A naive hard-bound import lib loadedunder Node but crashed Bun; the delay-load hook fixes that.)
Distribution (all-in-one + smart loader)
lib/abieos.tsresolves./abieos-${process.platform}-${process.arch}.node,falling back to
./abieos.node(also written by a source build).scripts/copy-module.mjswrites the platform-named binary + genericfallback and handles single-/multi-config generator layouts.
linux-x64,win32-x64.Verified locally (Windows, this PR's binary)
eosio→0000000000EA3055→ back).require) consumer path works.npm pack --dry-run: 14 files, shipsdist/abieos-{linux-x64,win32-x64}.nodeabieos.nodefallback, version4.2.0-f7d5b45.macOS is not validated locally (no macOS dev env) — CI is its gate.
CI
Adds
build-windows(windows-latest, MinGW-w64 viamsys2/setup-msys2) andbuild-macos(macos-14/arm64 +macos-13/x64). Each builds, tests, anduploads its
.nodeartifact; the Linux job now uploads its artifact too.Two separate gates⚠️
darwin-x64/darwin-arm64prebuilts must be committed from the macOS CI artifacts before
npm publish— Mach-O cannot be cross-built from Linux/Windows. Untilthen the loader directs macOS users to
npm run build:mac. Do nottag/publish
4.2.0until the darwin binaries are in the tree.Notes
-Wstringop-overflowwarning in vendoredabieos/include/eosio/bitset.hpp(not an error; Clang/Linux CI doesn't).