Skip to content

feat: Windows (MinGW-w64) + macOS support + multi-platform prebuilts (4.2.0)#14

Merged
igorls merged 4 commits into
masterfrom
feat/windows-macos-build
May 18, 2026
Merged

feat: Windows (MinGW-w64) + macOS support + multi-platform prebuilts (4.2.0)#14
igorls merged 4 commits into
masterfrom
feat/windows-macos-build

Conversation

@igorls

@igorls igorls commented May 17, 2026

Copy link
Copy Markdown
Member

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. abieos itself is unchanged (submodule still f7d5b45); version
4.1.1-f7d5b454.2.0-f7d5b45.

Windows (MinGW-w64)

abieos cannot build with MSVC (its C++ needs libstdc++/libc++ semantics and
GCC/Clang extensions MSVC's STL/compiler lack) — CMakeLists.txt now fails
fast
on MSVC with guidance.

cmake-js only emits the MSVC node.lib and an #ifdef _MSC_VER-gated
delay-load hook. The MinGW path instead:

  • generates a delay-import library from the Node-API .def with dlltool;
  • compiles its own delay-load hook (src/win_delay_load_hook.cc) that binds
    napi_* 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 loaded
under Node but crashed Bun; the delay-load hook fixes that.)

Distribution (all-in-one + smart loader)

  • lib/abieos.ts resolves ./abieos-${process.platform}-${process.arch}.node,
    falling back to ./abieos.node (also written by a source build).
  • scripts/copy-module.mjs writes the platform-named binary + generic
    fallback and handles single-/multi-config generator layouts.
  • Committed prebuilts: linux-x64, win32-x64.

Verified locally (Windows, this PR's binary)

  • Node 24 full suite: 55/55 passing via the platform-aware loader.
  • Bun: end-to-end JSON⇄hex round-trip (eosio0000000000EA3055 → back).
  • CJS (require) consumer path works.
  • npm pack --dry-run: 14 files, ships dist/abieos-{linux-x64,win32-x64}.node
    • the abieos.node fallback, version 4.2.0-f7d5b45.

macOS is not validated locally (no macOS dev env) — CI is its gate.

CI

Adds build-windows (windows-latest, MinGW-w64 via msys2/setup-msys2) and
build-macos (macos-14/arm64 + macos-13/x64). Each builds, tests, and
uploads its .node artifact; the Linux job now uploads its artifact too.

Two separate gates ⚠️

  1. Merge gate: CI green across Linux + Windows + macOS + Bun.
  2. Publish gate (follow-up): the darwin-x64 / darwin-arm64
    prebuilts must be committed from the macOS CI artifacts before
    npm publish
    — Mach-O cannot be cross-built from Linux/Windows. Until
    then the loader directs macOS users to npm run build:mac. Do not
    tag/publish 4.2.0 until the darwin binaries are in the tree.

Notes

  • GCC 13 emits one benign -Wstringop-overflow warning in vendored
    abieos/include/eosio/bitset.hpp (not an error; Clang/Linux CI doesn't).

…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.
Copilot AI review requested due to automatic review settings May 17, 2026 21:32

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.

Comment thread lib/abieos.ts
Comment on lines +15 to +18
const candidates = [
`./abieos-${process.platform}-${process.arch}.node`,
'./abieos.node',
];

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

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',
    ];

Comment thread scripts/copy-module.mjs Outdated
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";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The dirname function is imported from node:path but is not used anywhere in the script. It should be removed to keep the code clean.

import { join } from "node:path";

Comment thread scripts/copy-module.mjs Outdated

if (!source) {
console.log(`${MODULE_NAME} not found under build/. Skipping copy.`);
process.exit(0);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Exiting with code 0 when the native module is not found can lead to silent failures in CI/CD pipelines. If the module is required for the package to function, this should be treated as an error.

    process.exit(1);

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

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.mjs rewrite that emits both abieos-<platform>-<arch>.node and a generic abieos.node.
  • MinGW-w64 Windows support: MSVC guard in CMake, dlltool-generated delay-import lib, and a new src/win_delay_load_hook.cc redirecting 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.

Comment thread README.md
Comment on lines +23 to +24
- Prebuilt binaries bundled: `linux-x64`, `win32-x64`, `darwin-x64`,
`darwin-arm64` (loaded automatically by platform/arch)
Comment thread CHANGELOG.md Outdated
### 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.
Comment thread CMakeLists.txt Outdated
# 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 "")
Comment thread scripts/copy-module.mjs Outdated
Comment on lines +22 to +44
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");
}
igorls added 3 commits May 17, 2026 19:06
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.
@igorls

igorls commented May 18, 2026

Copy link
Copy Markdown
Member Author

Review feedback addressed + coverage restored

Thanks @gemini-code-assist and @copilot-pull-request-reviewer. All comments resolved across 1552d7b, ef2752d, cf188c5:

gemini-code-assist

  • Loader search pathslib/abieos.ts now searches ./abieos-<platform>-<arch>.node, ./abieos.node, ../lib/abieos-<platform>-<arch>.node, ../lib/abieos.node, covering both the published (dist/) and source (lib/) layouts.
  • Unused dirname import — removed.
  • process.exit(0) on missing modulescripts/copy-module.mjs now exits 1 with an actionable message (no silent CI pass).

copilot-pull-request-reviewer

  • CMAKE_SHARED_LINKER_FLAGS "" wiped all flags — now strips only the MSVC /DELAYLOAD:* token via string(REGEX REPLACE …), preserving toolchain/preset/LDFLAGS flags.
  • findRecursively could pick a stale binary — removed the recursive scan; the three explicit candidates cover every cmake-js generator layout, and a miss is now a hard error.
  • README/CHANGELOG over-claimed darwin prebuilts — resolved by making the claim true: the darwin-arm64 + darwin-x64 universal2 (x86_64+arm64) binaries from the green build-macos CI artifacts are now committed. All four prebuilts (linux-x64, win32-x64, darwin-arm64, darwin-x64) ship; npm i is zero-build everywhere. No remaining publish gate.

Coverage — the loader is now an exported pure resolveNative(); test/loader.test.js exercises every branch. Coveralls is back to 100% (was −2.7%); 60/60 tests.

Also: GitHub's Intel macos-13 runner was queued >3 h (Intel runners being retired), so build-macos is now a single Apple-Silicon job producing one universal2 binary published under both darwin names — and a coverage/-leak into the npm tarball (36→16 files) was fixed in .npmignore.

@gemini-code-assist

Copy link
Copy Markdown

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.

Copilot finished work on behalf of igorls May 18, 2026 01:37
@igorls igorls merged commit 7011341 into master May 18, 2026
8 checks passed
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.

2 participants