@gjsify/gtk-runtime-darwin-arm64 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/index.d.ts +11 -0
- package/index.js +24 -0
- package/package.json +46 -0
- package/scripts/build-gtk-runtime.mjs +240 -0
package/README.md
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# @gjsify/gtk-runtime-darwin-arm64
|
|
2
|
+
|
|
3
|
+
Batteries-included, **relocated** GTK / GObject-Introspection runtime bundle for
|
|
4
|
+
**macOS arm64**. It lets [`@gjsify/node-gi`](../node-gi) load `gi://` namespaces
|
|
5
|
+
(GLib · GObject · Gio · cairo · Pango · Graphene · Gdk) with **no Homebrew GTK**
|
|
6
|
+
installed on the host — Phase 2 of cross-platform node-gi.
|
|
7
|
+
|
|
8
|
+
Platform-gated (`os: ["darwin"]`, `cpu: ["arm64"]`), tier 3 (experimental). On any
|
|
9
|
+
other platform npm skips it and `@gjsify/node-gi` falls back to a system/Homebrew
|
|
10
|
+
GTK. The heavy `gtk/` payload is **not committed** — it is built on a macOS CI
|
|
11
|
+
runner (`scripts/build-gtk-runtime.mjs`) and shipped via the package tarball /
|
|
12
|
+
staged into node-gi's `prebuilds/darwin-arm64/gtk/`.
|
|
13
|
+
|
|
14
|
+
## Layout
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
gtk/
|
|
18
|
+
lib/ relocated dylibs (@loader_path-linked, ad-hoc re-signed)
|
|
19
|
+
girepository-1.0/ typelibs (GLib/GObject/Gio/cairo/Pango/Graphene/Gdk/…)
|
|
20
|
+
manifest.json counts + sizes + dylib list
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## How it is built (the relocation, the crux)
|
|
24
|
+
|
|
25
|
+
`node scripts/build-gtk-runtime.mjs` runs on a macOS runner **with** a build-time
|
|
26
|
+
Homebrew GTK stack (the closure *source*, not shipped):
|
|
27
|
+
|
|
28
|
+
1. **Collect** — walk the dylib graph with `otool -L`, recursively, from the
|
|
29
|
+
typelib-backing libraries the display-free conformance loads
|
|
30
|
+
(glib/gobject/gio + girepository + cairo + pango + graphene + gdk-pixbuf +
|
|
31
|
+
gtk4). Everything under `$(brew --prefix)/lib` is copied flat into `gtk/lib`;
|
|
32
|
+
`/usr/lib` + `/System` libraries are left as OS-provided.
|
|
33
|
+
2. **Relocate** — for each bundled dylib, rewrite its own id **and** every
|
|
34
|
+
sibling reference to `@loader_path/<leaf>` with `install_name_tool`, then
|
|
35
|
+
**ad-hoc re-sign** it (`codesign --force --sign -`). Re-signing is mandatory
|
|
36
|
+
on Apple silicon: `install_name_tool` invalidates the code signature and dyld
|
|
37
|
+
refuses to load a mis-signed dylib. After this, **no bundled library
|
|
38
|
+
references `/opt/homebrew`** — the bundle is portable.
|
|
39
|
+
3. **Typelibs** — copy the typelib set into `gtk/girepository-1.0`.
|
|
40
|
+
4. **Addon (optional, `--addon`)** — relocate a *copy* of the node-gi addon
|
|
41
|
+
(`node_gi.node`): rewrite its `/opt/homebrew/...` refs to `@rpath/<leaf>` and
|
|
42
|
+
add `@loader_path/gtk/lib` as an rpath, so the addon loads the **bundled**
|
|
43
|
+
`libgirepository` with no Homebrew. This is the env-free path.
|
|
44
|
+
|
|
45
|
+
## How node-gi finds it
|
|
46
|
+
|
|
47
|
+
`@gjsify/node-gi`'s loader (`gtk-runtime.js`) resolves the bundle from, in order:
|
|
48
|
+
|
|
49
|
+
1. `GJSIFY_GTK_RUNTIME` (explicit bundle dir),
|
|
50
|
+
2. node-gi's own `prebuilds/<platform>-<arch>/gtk/` (the CI/dev staging path),
|
|
51
|
+
3. the sibling monorepo dir `../gtk-runtime-<platform>-<arch>/gtk`,
|
|
52
|
+
4. `require.resolve('@gjsify/gtk-runtime-<platform>-<arch>')` (the published dep).
|
|
53
|
+
|
|
54
|
+
On a match (darwin only) node-gi makes the bundle genuinely **env-free**:
|
|
55
|
+
|
|
56
|
+
- **typelibs** — it prepends `gtk/girepository-1.0` to the GIRepository search
|
|
57
|
+
path via `prependSearchPath` (a runtime API, no env needed).
|
|
58
|
+
- **dylibs** — on **Node** it **re-execs the process once**
|
|
59
|
+
(`maybeReexecForGtkRuntime`, from `index.js`, before the addon loads) with
|
|
60
|
+
`DYLD_FALLBACK_LIBRARY_PATH=gtk/lib`. (Bun/Deno skip the re-exec — their
|
|
61
|
+
`argv`/`execArgv` differ; they still get the env-free typelibs above, and their
|
|
62
|
+
DYLD path for the non-addon-linked backers is a follow-up.)
|
|
63
|
+
dyld only reads that variable at **launch**, so a JS-time `process.env` mutation
|
|
64
|
+
cannot help the running process — but a one-shot re-exec (guarded by a
|
|
65
|
+
`GJSIFY_GTK_REEXEC` sentinel so it fires at most once) lands the fallback for the
|
|
66
|
+
fresh dyld. GObject-Introspection then resolves every type's `get_type()` and the
|
|
67
|
+
Pango/Gdk/Graphene backers by leaf soname from the bundle. This is exactly what
|
|
68
|
+
the Homebrew-based CI leg relies on, so it is a known-good path.
|
|
69
|
+
|
|
70
|
+
The re-exec is a no-op off darwin, without a bundle, or once the fallback already
|
|
71
|
+
covers the bundle — so it costs one extra `exec` only on the first macOS load of a
|
|
72
|
+
batteries-included install, and nothing elsewhere.
|
|
73
|
+
|
|
74
|
+
## Scope & what a full windowing bundle still needs
|
|
75
|
+
|
|
76
|
+
This bundle targets the **display-free conformance closure only**. The full
|
|
77
|
+
GTK windowing/display stack additionally needs: the Quartz GDK backend, the
|
|
78
|
+
`gdk-pixbuf` `loaders.cache` + loader modules, compiled GSettings schemas
|
|
79
|
+
(`glib-compile-schemas`), the Adwaita icon theme + `icon-theme.cache`, and
|
|
80
|
+
Fontconfig cache/config — none of which are collected here.
|
|
81
|
+
|
|
82
|
+
**Env-free mechanism (macOS):** because dyld captures
|
|
83
|
+
`DYLD_FALLBACK_LIBRARY_PATH` only at launch, node-gi re-execs once with it set (see
|
|
84
|
+
above) rather than relying on the caller to export it. This covers **all** the
|
|
85
|
+
display-free namespaces uniformly — both the addon-linked core
|
|
86
|
+
(GLib/GObject/Gio/cairo) and the non-addon-linked backers (Pango/Graphene/Gdk),
|
|
87
|
+
including the `get_type()` leaf lookups that `registerClass` subclassing needs. A
|
|
88
|
+
native `dlopen`-preload at addon load would avoid even the single re-exec, but it
|
|
89
|
+
would require changing the node-gi addon; the re-exec keeps the consumed addon
|
|
90
|
+
prebuild untouched.
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
/** Absolute path to the bundle root (contains `lib/` + `girepository-1.0/`). */
|
|
3
|
+
export const bundleDir: string;
|
|
4
|
+
/** Absolute path to the relocated dylib dir (`@loader_path`-linked). */
|
|
5
|
+
export const libDir: string;
|
|
6
|
+
/** Absolute path to the typelib dir (feed to GIRepository.prepend_search_path). */
|
|
7
|
+
export const typelibDir: string;
|
|
8
|
+
/** Whether the bundle payload is actually present on disk (it is built on CI). */
|
|
9
|
+
export const isPresent: boolean;
|
|
10
|
+
declare const _default: { bundleDir: string; libDir: string; typelibDir: string; isPresent: boolean };
|
|
11
|
+
export default _default;
|
package/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// @gjsify/gtk-runtime-darwin-arm64 — path helpers for the relocated GTK runtime
|
|
3
|
+
// bundle. @gjsify/node-gi resolves this package (optional, os/cpu-gated) to find
|
|
4
|
+
// the bundled typelib + dylib dirs when no Homebrew GTK is present. The heavy
|
|
5
|
+
// `gtk/` payload is produced by scripts/build-gtk-runtime.mjs on a macOS runner.
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { dirname, join } from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
|
|
10
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
11
|
+
|
|
12
|
+
/** Absolute path to the bundle root (contains `lib/` + `girepository-1.0/`). */
|
|
13
|
+
export const bundleDir = join(here, 'gtk');
|
|
14
|
+
|
|
15
|
+
/** Absolute path to the relocated dylib dir (`@loader_path`-linked). */
|
|
16
|
+
export const libDir = join(bundleDir, 'lib');
|
|
17
|
+
|
|
18
|
+
/** Absolute path to the typelib dir (feed to GIRepository.prepend_search_path). */
|
|
19
|
+
export const typelibDir = join(bundleDir, 'girepository-1.0');
|
|
20
|
+
|
|
21
|
+
/** Whether the bundle payload is actually present on disk (it is built on CI). */
|
|
22
|
+
export const isPresent = existsSync(typelibDir) && existsSync(libDir);
|
|
23
|
+
|
|
24
|
+
export default { bundleDir, libDir, typelibDir, isPresent };
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@gjsify/gtk-runtime-darwin-arm64",
|
|
3
|
+
"version": "0.19.0",
|
|
4
|
+
"description": "Batteries-included, relocated GTK/GObject-Introspection runtime bundle for macOS arm64 — lets @gjsify/node-gi load gi:// namespaces (GLib/GObject/Gio/cairo/Pango/Graphene/Gdk) with NO Homebrew GTK. Platform-gated (darwin/arm64 only).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"os": [
|
|
8
|
+
"darwin"
|
|
9
|
+
],
|
|
10
|
+
"cpu": [
|
|
11
|
+
"arm64"
|
|
12
|
+
],
|
|
13
|
+
"main": "./index.js",
|
|
14
|
+
"types": "./index.d.ts",
|
|
15
|
+
"exports": {
|
|
16
|
+
".": {
|
|
17
|
+
"types": "./index.d.ts",
|
|
18
|
+
"default": "./index.js"
|
|
19
|
+
},
|
|
20
|
+
"./gtk/girepository-1.0": "./gtk/girepository-1.0",
|
|
21
|
+
"./gtk/lib": "./gtk/lib"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"index.js",
|
|
25
|
+
"index.d.ts",
|
|
26
|
+
"scripts",
|
|
27
|
+
"gtk"
|
|
28
|
+
],
|
|
29
|
+
"gjsify": {
|
|
30
|
+
"runtimes": {
|
|
31
|
+
"gjs": "none",
|
|
32
|
+
"node": "polyfill",
|
|
33
|
+
"browser": "none",
|
|
34
|
+
"nativescript": "none"
|
|
35
|
+
},
|
|
36
|
+
"tier": 3
|
|
37
|
+
},
|
|
38
|
+
"scripts": {
|
|
39
|
+
"build:bundle": "node scripts/build-gtk-runtime.mjs"
|
|
40
|
+
},
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/gjsify/gjsify.git",
|
|
44
|
+
"directory": "packages/node-gi/gtk-runtime-darwin-arm64"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
// Collect + relocate a standalone, batteries-included GTK/GObject-Introspection
|
|
3
|
+
// runtime bundle for macOS arm64 — so @gjsify/node-gi's display-free conformance
|
|
4
|
+
// runs with NO Homebrew GTK on the host (Phase 2 of cross-platform node-gi).
|
|
5
|
+
//
|
|
6
|
+
// node scripts/build-gtk-runtime.mjs [--out <dir>] [--addon <node_gi.node>] [--stage <dir>]
|
|
7
|
+
//
|
|
8
|
+
// Runs ONLY on darwin/arm64 with a build-time Homebrew GTK stack installed (the
|
|
9
|
+
// closure SOURCE — not shipped). It:
|
|
10
|
+
// 1. Walks the dylib graph (`otool -L`, recursively) from the typelib-backing
|
|
11
|
+
// libraries the conformance loads (glib/gobject/gio + girepository + cairo +
|
|
12
|
+
// pango + graphene + gdk-pixbuf + gtk4), collecting every Homebrew dylib.
|
|
13
|
+
// 2. Copies them flat into <out>/lib and RELOCATES each: rewrites its own id +
|
|
14
|
+
// every sibling reference to `@loader_path/<leaf>` (via install_name_tool),
|
|
15
|
+
// leaving /usr/lib + /System refs untouched, then AD-HOC RE-SIGNS it
|
|
16
|
+
// (`codesign -s -`) — mandatory on Apple silicon: install_name_tool
|
|
17
|
+
// invalidates the code signature and dyld refuses an unsigned/mis-signed
|
|
18
|
+
// dylib.
|
|
19
|
+
// 3. Copies the typelib set into <out>/girepository-1.0.
|
|
20
|
+
// 4. (optional) Relocates a COPY of the node-gi addon (--addon) so it loads the
|
|
21
|
+
// BUNDLED libgirepository via `@rpath` (add_rpath @loader_path/gtk/lib) with
|
|
22
|
+
// NO Homebrew — the env-free path the core conformance leg exercises.
|
|
23
|
+
//
|
|
24
|
+
// The result is portable: none of the bundled libraries reference /opt/homebrew.
|
|
25
|
+
// Reference: GJS ships no relocation; the technique mirrors macOS app-bundle
|
|
26
|
+
// dylib fix-up (install_name_tool + @loader_path + ad-hoc codesign).
|
|
27
|
+
import { execFileSync } from 'node:child_process';
|
|
28
|
+
import { copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, realpathSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
29
|
+
import { basename, dirname, join } from 'node:path';
|
|
30
|
+
import { fileURLToPath } from 'node:url';
|
|
31
|
+
|
|
32
|
+
const pkgRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
33
|
+
|
|
34
|
+
// --- args ------------------------------------------------------------------
|
|
35
|
+
function argValue(flag) {
|
|
36
|
+
const i = process.argv.indexOf(flag);
|
|
37
|
+
return i >= 0 && i + 1 < process.argv.length ? process.argv[i + 1] : undefined;
|
|
38
|
+
}
|
|
39
|
+
const OUT = argValue('--out') ?? join(pkgRoot, 'gtk');
|
|
40
|
+
const ADDON = argValue('--addon'); // optional: a node_gi.node to relocate a copy of
|
|
41
|
+
const STAGE = argValue('--stage'); // optional: sibling layout <stage>/{node_gi.node,gtk/}
|
|
42
|
+
|
|
43
|
+
if (process.platform !== 'darwin' || process.arch !== 'arm64') {
|
|
44
|
+
console.error(`build-gtk-runtime: only supported on darwin/arm64, not ${process.platform}/${process.arch}`);
|
|
45
|
+
process.exit(2);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const sh = (bin, args) => execFileSync(bin, args, { encoding: 'utf8' });
|
|
49
|
+
|
|
50
|
+
const brewPrefix = sh('brew', ['--prefix']).trim();
|
|
51
|
+
const brewLib = join(brewPrefix, 'lib');
|
|
52
|
+
const brewTypelibs = join(brewLib, 'girepository-1.0');
|
|
53
|
+
if (!existsSync(brewLib)) {
|
|
54
|
+
console.error(`build-gtk-runtime: ${brewLib} not found — install the GTK stack first (brew install gtk4 …)`);
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// The typelib-backing libraries the DISPLAY-FREE conformance closure loads.
|
|
59
|
+
// Version-agnostic base patterns matched against $(brew --prefix)/lib. The
|
|
60
|
+
// recursive otool walk pulls every transitive Homebrew dep (harfbuzz, fribidi,
|
|
61
|
+
// fontconfig, freetype, pixman, png, intl, pcre2, ffi, epoxy, …).
|
|
62
|
+
const SEED_PATTERNS = [
|
|
63
|
+
/^libgirepository-2\.0\..*\.dylib$/, // GIRepository (merged into glib on modern brew)
|
|
64
|
+
/^libglib-2\.0\..*\.dylib$/,
|
|
65
|
+
/^libgobject-2\.0\..*\.dylib$/,
|
|
66
|
+
/^libgio-2\.0\..*\.dylib$/,
|
|
67
|
+
/^libgmodule-2\.0\..*\.dylib$/,
|
|
68
|
+
/^libcairo(-gobject|-script-interpreter)?\.[\d.]*dylib$/,
|
|
69
|
+
/^libpango-1\.0\..*\.dylib$/,
|
|
70
|
+
/^libpangocairo-1\.0\..*\.dylib$/,
|
|
71
|
+
/^libpangoft2-1\.0\..*\.dylib$/,
|
|
72
|
+
/^libgraphene-1\.0\..*\.dylib$/,
|
|
73
|
+
/^libgdk_pixbuf-2\.0\..*\.dylib$/,
|
|
74
|
+
/^libgtk-4\..*\.dylib$/, // provides the Gdk typelib's backing library
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
function isSystemPath(p) {
|
|
78
|
+
return p.startsWith('/usr/lib/') || p.startsWith('/System/');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Parse an `otool -L <lib>` output into the list of dependency install paths
|
|
82
|
+
// (skipping the first line — the library's own id — and system libraries).
|
|
83
|
+
function otoolDeps(libPath) {
|
|
84
|
+
let out;
|
|
85
|
+
try {
|
|
86
|
+
out = sh('otool', ['-L', libPath]);
|
|
87
|
+
} catch {
|
|
88
|
+
return [];
|
|
89
|
+
}
|
|
90
|
+
const lines = out.split('\n').slice(1); // line 0 is the id, not a dep
|
|
91
|
+
const deps = [];
|
|
92
|
+
for (const line of lines) {
|
|
93
|
+
const trimmed = line.trim();
|
|
94
|
+
if (!trimmed) continue;
|
|
95
|
+
const path = trimmed.split(' (')[0];
|
|
96
|
+
if (!path || isSystemPath(path)) continue;
|
|
97
|
+
deps.push(path);
|
|
98
|
+
}
|
|
99
|
+
return deps;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Resolve a dependency's leaf to the real Homebrew file (following symlinks),
|
|
103
|
+
// or null when it is not a Homebrew library we bundle.
|
|
104
|
+
function resolveInBrew(leaf) {
|
|
105
|
+
const candidate = join(brewLib, leaf);
|
|
106
|
+
if (!existsSync(candidate)) return null;
|
|
107
|
+
try {
|
|
108
|
+
return realpathSync(candidate);
|
|
109
|
+
} catch {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// --- 1. discover the closure ----------------------------------------------
|
|
115
|
+
console.log(`build-gtk-runtime: brew prefix ${brewPrefix}`);
|
|
116
|
+
const seeds = readdirSync(brewLib).filter((f) => SEED_PATTERNS.some((re) => re.test(f)));
|
|
117
|
+
if (seeds.length === 0) {
|
|
118
|
+
console.error('build-gtk-runtime: no seed libraries found — is the GTK stack installed?');
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
console.log(`build-gtk-runtime: ${seeds.length} seed libraries: ${seeds.join(', ')}`);
|
|
122
|
+
|
|
123
|
+
const bundled = new Map(); // leaf -> real source path
|
|
124
|
+
const queue = [...seeds];
|
|
125
|
+
while (queue.length) {
|
|
126
|
+
const leaf = queue.shift();
|
|
127
|
+
if (bundled.has(leaf)) continue;
|
|
128
|
+
const real = resolveInBrew(leaf);
|
|
129
|
+
if (!real) continue; // system / non-brew dep — leave as-is
|
|
130
|
+
bundled.set(leaf, real);
|
|
131
|
+
for (const dep of otoolDeps(real)) {
|
|
132
|
+
const depLeaf = basename(dep);
|
|
133
|
+
if (!bundled.has(depLeaf)) queue.push(depLeaf);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
console.log(`build-gtk-runtime: closure = ${bundled.size} dylibs`);
|
|
137
|
+
|
|
138
|
+
// --- 2. copy + relocate + re-sign -----------------------------------------
|
|
139
|
+
const libOut = join(OUT, 'lib');
|
|
140
|
+
rmSync(OUT, { recursive: true, force: true });
|
|
141
|
+
mkdirSync(libOut, { recursive: true });
|
|
142
|
+
|
|
143
|
+
for (const [leaf, src] of bundled) {
|
|
144
|
+
copyFileSync(src, join(libOut, leaf));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const bundledLeaves = new Set(bundled.keys());
|
|
148
|
+
function relocate(libPath, { id } = {}) {
|
|
149
|
+
// Own id (dylibs only).
|
|
150
|
+
if (id) execFileSync('install_name_tool', ['-id', `@loader_path/${basename(libPath)}`, libPath]);
|
|
151
|
+
// Rewrite every dependency that points at a library WE bundle → @loader_path.
|
|
152
|
+
for (const dep of otoolDeps(libPath)) {
|
|
153
|
+
if (bundledLeaves.has(basename(dep))) {
|
|
154
|
+
execFileSync('install_name_tool', ['-change', dep, `@loader_path/${basename(dep)}`, libPath]);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
// Ad-hoc re-sign — install_name_tool invalidated the signature (arm64 hard req).
|
|
158
|
+
execFileSync('codesign', ['--force', '--sign', '-', libPath]);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
for (const leaf of bundledLeaves) {
|
|
162
|
+
relocate(join(libOut, leaf), { id: true });
|
|
163
|
+
}
|
|
164
|
+
console.log(`build-gtk-runtime: relocated + re-signed ${bundledLeaves.size} dylibs → @loader_path`);
|
|
165
|
+
|
|
166
|
+
// --- 3. typelibs -----------------------------------------------------------
|
|
167
|
+
const typelibOut = join(OUT, 'girepository-1.0');
|
|
168
|
+
mkdirSync(typelibOut, { recursive: true });
|
|
169
|
+
let typelibCount = 0;
|
|
170
|
+
if (existsSync(brewTypelibs)) {
|
|
171
|
+
for (const f of readdirSync(brewTypelibs)) {
|
|
172
|
+
if (f.endsWith('.typelib')) {
|
|
173
|
+
copyFileSync(join(brewTypelibs, f), join(typelibOut, f));
|
|
174
|
+
typelibCount++;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
console.log(`build-gtk-runtime: copied ${typelibCount} typelibs`);
|
|
179
|
+
|
|
180
|
+
// --- 4. optional: relocate a copy of the node-gi addon --------------------
|
|
181
|
+
// The addon (built against Homebrew) carries absolute /opt/homebrew refs. Rewrite
|
|
182
|
+
// them to @rpath/<leaf> + add an rpath to the SIBLING bundle so it loads the
|
|
183
|
+
// bundled libgirepository with NO Homebrew — the env-free core-conformance path.
|
|
184
|
+
if (ADDON) {
|
|
185
|
+
if (!existsSync(ADDON)) {
|
|
186
|
+
console.error(`build-gtk-runtime: --addon ${ADDON} not found`);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
const stageDir = STAGE ?? join(OUT, '..', 'staged');
|
|
190
|
+
mkdirSync(stageDir, { recursive: true });
|
|
191
|
+
const addonDest = join(stageDir, 'node_gi.node');
|
|
192
|
+
copyFileSync(ADDON, addonDest);
|
|
193
|
+
// Point the addon at the bundle staged as its sibling `gtk/`.
|
|
194
|
+
cpSync(OUT, join(stageDir, 'gtk'), { recursive: true });
|
|
195
|
+
|
|
196
|
+
for (const dep of otoolDeps(addonDest)) {
|
|
197
|
+
// Any non-system dep (Homebrew absolute path OR @rpath leaf we bundle).
|
|
198
|
+
if (bundledLeaves.has(basename(dep)) || dep.startsWith(brewPrefix) || dep.startsWith('/usr/local/')) {
|
|
199
|
+
execFileSync('install_name_tool', ['-change', dep, `@rpath/${basename(dep)}`, addonDest]);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
try {
|
|
203
|
+
execFileSync('install_name_tool', ['-add_rpath', '@loader_path/gtk/lib', addonDest]);
|
|
204
|
+
} catch {
|
|
205
|
+
// rpath already present — fine.
|
|
206
|
+
}
|
|
207
|
+
execFileSync('codesign', ['--force', '--sign', '-', addonDest]);
|
|
208
|
+
console.log(`build-gtk-runtime: relocated addon → ${addonDest} (rpath @loader_path/gtk/lib)`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// --- manifest + size -------------------------------------------------------
|
|
212
|
+
function dirSize(dir) {
|
|
213
|
+
let total = 0;
|
|
214
|
+
for (const f of readdirSync(dir, { withFileTypes: true })) {
|
|
215
|
+
const p = join(dir, f.name);
|
|
216
|
+
total += f.isDirectory() ? dirSize(p) : statSync(p).size;
|
|
217
|
+
}
|
|
218
|
+
return total;
|
|
219
|
+
}
|
|
220
|
+
const libBytes = dirSize(libOut);
|
|
221
|
+
const typelibBytes = dirSize(typelibOut);
|
|
222
|
+
const manifest = {
|
|
223
|
+
platform: 'darwin-arm64',
|
|
224
|
+
generatedFrom: brewPrefix,
|
|
225
|
+
dylibs: bundledLeaves.size,
|
|
226
|
+
typelibs: typelibCount,
|
|
227
|
+
libBytes,
|
|
228
|
+
typelibBytes,
|
|
229
|
+
totalBytes: libBytes + typelibBytes,
|
|
230
|
+
dylibList: [...bundledLeaves].sort(),
|
|
231
|
+
};
|
|
232
|
+
writeFileSync(join(OUT, 'manifest.json'), JSON.stringify(manifest, null, 2));
|
|
233
|
+
|
|
234
|
+
const mb = (n) => (n / 1024 / 1024).toFixed(1);
|
|
235
|
+
console.log(
|
|
236
|
+
`build-gtk-runtime: DONE → ${OUT}\n` +
|
|
237
|
+
` dylibs: ${bundledLeaves.size} (${mb(libBytes)} MiB)\n` +
|
|
238
|
+
` typelibs: ${typelibCount} (${mb(typelibBytes)} MiB)\n` +
|
|
239
|
+
` total: ${mb(manifest.totalBytes)} MiB`,
|
|
240
|
+
);
|