Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .github/workflows/main.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -39,13 +39,19 @@ jobs:
mingw-w64-x86_64-gcc
mingw-w64-x86_64-pkgconf
mingw-w64-x86_64-python
mingw-w64-x86_64-ntldd-git
mingw-w64-x86_64-gobject-introspection
mingw-w64-x86_64-gtk3
mingw-w64-x86_64-cairo
mingw-w64-x86_64-gtk3
mingw-w64-x86_64-gstreamer
mingw-w64-x86_64-gst-plugins-good
mingw-w64-x86_64-gst-plugins-bad
mingw-w64-x86_64-libsoup3
mingw-w64-x86_64-gtk4
mingw-w64-x86_64-libadwaita
mingw-w64-x86_64-gtksourceview5
mingw-w64-x86_64-graphene
mingw-w64-x86_64-adwaita-icon-theme

- name: Install & Build
env:
Expand All @@ -64,6 +70,27 @@ jobs:
--skip=callback \
tests/__run__.js

# Make the freshly-built addon a self-contained prebuilt: bundle the GTK4
# runtime (DLLs + GI typelibs + data) next to the .node so Windows users
# can `npm install node-gtk` with no MSYS2/compiler. lib/native.js wires
# this bundle up automatically at load time.
- name: Bundle GTK4 runtime
run: |
ABI=$(node -p "process.versions.modules")
./scripts/windows-bundle-runtime.sh "lib/binding/node-v${ABI}-win32-x64"

# Publish the prebuilt to S3 on a `[publish binary]` commit, like the
# Linux/macOS jobs do via scripts/ci.sh.
- name: Publish prebuilt
if: contains(github.event.head_commit.message, '[publish binary]')
env:
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
run: |
npx node-pre-gyp package
npx node-pre-gyp publish
npx node-pre-gyp info

build:
name: ${{ matrix.os }} - nodejs ${{ matrix.node }}
runs-on: ${{ matrix.os }}
Expand Down
156 changes: 156 additions & 0 deletions .github/workflows/test-windows-prebuilt.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
name: test-windows-prebuilt
#
# Answers: "do prebuilt Windows binaries let users `npm install node-gtk` and use
# it WITHOUT compiling?"
#
# build-prebuilt - build the addon under MSYS2/MinGW (as today), then bundle
# its full DLL closure + GI typelibs next to the .node and
# upload it as an artifact (this is the candidate prebuilt).
# consume-clean - a SEPARATE, clean Windows runner with NO MSYS2 and NO
# compiler. Installs node-gtk's JS deps with --ignore-scripts
# (so nothing is ever built), drops in the prebuilt, and runs
# a smoke test that loads GTK and exercises GObject-Introspection.
#
on:
push:
branches:
- test-windows-prebuilt
workflow_dispatch:

jobs:
build-prebuilt:
name: build prebuilt - nodejs ${{ matrix.node }}
runs-on: windows-2022
strategy:
fail-fast: false
matrix:
node:
- 22
- 20
defaults:
run:
shell: msys2 {0}
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}
- uses: msys2/setup-msys2@v2
with:
msystem: MINGW64
path-type: inherit
update: true
install: |
git
make
mingw-w64-x86_64-gcc
mingw-w64-x86_64-pkgconf
mingw-w64-x86_64-python
mingw-w64-x86_64-ntldd-git
mingw-w64-x86_64-gobject-introspection
mingw-w64-x86_64-cairo
mingw-w64-x86_64-gtk4
mingw-w64-x86_64-libadwaita
mingw-w64-x86_64-gtksourceview5
mingw-w64-x86_64-graphene
mingw-w64-x86_64-adwaita-icon-theme

- name: Build from source
env:
GYP_GENERATORS: make
CC: gcc
CXX: g++
run: |
./windows/mingw_include_extra.sh
export MINGW_WINDOWS_PATH=$(./windows/mingw_windows_path.sh)
export PATH="/mingw64/bin:/usr/bin:$PATH"
npm install --build-from-source

- name: Bundle GTK4 runtime (DLLs + typelibs + data)
run: |
ABI=$(node -p "process.versions.modules")
BINDING="lib/binding/node-v${ABI}-win32-x64"
echo "ABI=$ABI BINDING=$BINDING"
ls -la "$BINDING"
./scripts/windows-bundle-runtime.sh "$BINDING"

- name: Report compressed download size (what users fetch)
run: |
npx node-pre-gyp package >/dev/null 2>&1 || true
TARBALL=$(find build/stage -name '*.tar.gz' 2>/dev/null | head -1)
if [ -n "$TARBALL" ]; then
echo "## node-pre-gyp tarball (gzip, the S3 download): $(du -h "$TARBALL" | cut -f1) -> $TARBALL"
else
echo "## (node-pre-gyp package produced no tarball; uncompressed size is in the bundle step)"
fi

- name: Upload prebuilt artifact
uses: actions/upload-artifact@v4
with:
name: prebuilt-node-${{ matrix.node }}
path: lib/binding/
if-no-files-found: error

consume-clean:
name: consume prebuilt (clean machine) - nodejs ${{ matrix.node }}
needs: build-prebuilt
runs-on: windows-latest
strategy:
fail-fast: false
matrix:
node:
- 22
- 20
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node }}

# Install ONLY the JS dependencies, never the native build hook.
# This is what a user gets when a prebuilt is available: no compiler runs.
- name: Install JS deps (no build)
shell: bash
run: npm install --ignore-scripts

- name: Download prebuilt
uses: actions/download-artifact@v4
with:
name: prebuilt-node-${{ matrix.node }}
path: lib/binding/

- name: Show what we got
shell: bash
run: |
echo "Tools available on this runner (expect NONE of these):"
(which gcc && echo "gcc FOUND") || echo "no gcc (good)"
(where.exe pacman && echo "pacman FOUND") || echo "no pacman (good)"
echo "---- bundled prebuilt ----"
ls -la lib/binding/*/ || true

# Negative control: move the bundled typelibs aside so native.js cannot
# auto-wire; the addon must then fail to load. Proves the pass below comes
# from the bundle (not the runner's GTK), then restores it. Allowed to fail.
- name: Negative control (no bundled runtime — expected to fail)
continue-on-error: true
shell: bash
run: |
export PATH=$(echo "$PATH" | tr ':' '\n' | grep -viE 'mingw|msys|chocolatey' | paste -sd ':' -)
DIR=$(echo lib/binding/node-v*-win32-x64)
mv "$DIR/girepository-1.0" "$DIR/girepository-1.0.off"
if node -e "require('.')"; then
echo "UNEXPECTED - loaded without the bundled GTK runtime"
else
echo "expected failure - addon needs the bundled GTK runtime/typelibs"
fi
mv "$DIR/girepository-1.0.off" "$DIR/girepository-1.0"

# The real test: NO env vars set here, and the runner's own MinGW/MSYS is
# stripped from PATH to simulate a clean user machine. A pass proves
# lib/native.js auto-wires the bundled runtime by itself.
- name: Smoke test (auto-wired, simulated clean machine)
shell: bash
run: |
export PATH=$(echo "$PATH" | tr ':' '\n' | grep -viE 'mingw|msys|chocolatey' | paste -sd ':' -)
echo "gcc on PATH? $(which gcc || echo no)"
node scripts/windows-smoke-test.js
47 changes: 43 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,19 @@ or [PyGObject](https://pygobject.readthedocs.io). Please note this project is
currently in a _alpha_ state.

Supported Node.js versions: **20**, **22**, **24** (other versions may work but are untested)<br>
Pre-built binaries available for: **Linux**, **macOS**
Supported platforms:
- **Linux** — prebuilt binaries available
- **macOS** — prebuilt binaries available
- **Windows** — prebuilt binaries available (but read [Windows](#windows))


### Table of contents

- [Usage](#usage)
- [ES modules](#es-modules)
- [Documentation](#documentation)
- [TypeScript](#typescript)
- [Installing and building](#installing-and-building)
- [Installing](#installing)
- [Contributing](#contributing)

## Usage
Expand Down Expand Up @@ -162,9 +166,44 @@ script so it regenerates on install:

Run `npx node-gtk generate-types --help` for options.

## Installing and building
## Installing

1. Install `node-gtk` itself
2. Install the native libraries you use (see examples per platform below)

```sh
npm install node-gtk

# This installs a prebuilt binary when one is available for your platform and
# Node.js version, otherwise it falls back to building from source.
```

#### Linux

```sh
# archlinux
pacman -S gtk4

# ubuntu
apt install libgtk-4-1
```

#### macOS

```sh
brew install gtk4
```

#### Windows

Windows doesn't have the dependencies we need in a package manager, therefore
`node-gtk` ships prebuilt versions of GTK 4 / Adwaita runtime (DLLs, typelibs,
icons), so `npm install node-gtk` is all you need **if** your dependency is in
our [list of prebuilt libraries](./windows/runtime-libraries.txt).

### build from source

See [Installing & building](./doc/installation.md) for prebuilt-binary notes, per-platform build instructions (Linux, macOS, Windows), and how to run the tests and examples.
Building from source, or contributing? See [Building from source](./doc/building.md).

## Contributing

Expand Down
14 changes: 5 additions & 9 deletions doc/installation.md → doc/building.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Installing & building
# Building from source

Note that prebuilt binaries are available for common systems, in those cases building is not necessary.
This guide is for **contributors** and anyone building node-gtk from source.
Most users don't need it — `npm install node-gtk` ships prebuilt binaries (see
[Installing](../README.md#installing)). Build from source to hack on node-gtk,
or to target a platform/Node.js version that has no prebuilt.

### Table of contents

- [Target Platforms](#target-platforms)
- [Requirements](#requirements)
- [How to build on Ubuntu](#how-to-build-on-ubuntu)
- [How to build on Fedora](#how-to-build-on-fedora)
Expand All @@ -15,12 +17,6 @@ Note that prebuilt binaries are available for common systems, in those cases bui
- [Unit tests](#unit-tests)
- [Browser demo](#browser-demo)

##### Target Platforms

- **Linux**: prebuilt binaries available
- **macOS**: prebuilt binaries available
- **Windows**: no prebuilt binaries

### Requirements

- `git`
Expand Down
51 changes: 51 additions & 0 deletions lib/native.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,61 @@

const binary = require('@mapbox/node-pre-gyp')
const path = require('path')
const fs = require('fs')

const packagePath = path.resolve(path.join(__dirname,'../package.json'))
const bindingPath = binary.find(packagePath)

// On Windows, the prebuilt binary ships with its whole GTK runtime bundled in
// the same directory as the .node (DLLs, GObject-Introspection typelibs, and
// runtime data). Wire the process environment to that bundle BEFORE the addon
// is loaded, so a plain `npm install node-gtk` works with no MSYS2, no compiler
// and no manual PATH setup. Done in this file specifically because it must run
// before `require(bindingPath)` (the addon's DLL imports are resolved at load
// time) and before bootstrap.js requires the GIRepository typelib.
if (process.platform === 'win32')
setupBundledRuntime(path.dirname(bindingPath))

function setupBundledRuntime(bundleDir) {
// A self-contained prebuilt always bundles its typelibs; if that marker is
// absent the addon was built from source against a system GTK, so leave the
// environment alone.
const typelibDir = path.join(bundleDir, 'girepository-1.0')
if (!fs.existsSync(typelibDir))
return

const prepend = (name, value) => {
const cur = process.env[name]
process.env[name] = cur ? value + path.delimiter + cur : value
}
const exists = p => { try { return fs.existsSync(p) } catch (e) { return false } }

// 1) DLLs — both the addon's own imports and the namespace shared libraries
// GObject-Introspection loads at runtime via g_module_open().
prepend('PATH', bundleDir)

// 2) GI typelibs.
prepend('GI_TYPELIB_PATH', typelibDir)

// 3) Runtime data: icon themes, GtkSourceView languages/styles, schemas.
const share = path.join(bundleDir, 'share')
if (exists(share))
prepend('XDG_DATA_DIRS', share)
const schemas = path.join(share, 'glib-2.0', 'schemas')
if (exists(schemas) && !process.env.GSETTINGS_SCHEMA_DIR)
process.env.GSETTINGS_SCHEMA_DIR = schemas

// 4) gdk-pixbuf image loaders (made path-portable at bundle time). The
// loaders dir goes on PATH so g_module_open() of a bare loader name from
// the cache resolves.
const loadersDir = path.join(bundleDir, 'lib', 'gdk-pixbuf-2.0', '2.10.0', 'loaders')
if (exists(loadersDir))
prepend('PATH', loadersDir)
const loaderCache = path.join(bundleDir, 'lib', 'gdk-pixbuf-2.0', '2.10.0', 'loaders.cache')
if (exists(loaderCache) && !process.env.GDK_PIXBUF_MODULE_FILE)
process.env.GDK_PIXBUF_MODULE_FILE = loaderCache
}

const binding = require(bindingPath)

module.exports = binding
Loading
Loading