@gjsify/gtk-runtime-darwin-arm64 0.26.1 → 0.27.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 CHANGED
@@ -8,8 +8,16 @@ installed on the host — Phase 2 of cross-platform node-gi.
8
8
  Platform-gated (`os: ["darwin"]`, `cpu: ["arm64"]`), tier 3 (experimental). On any
9
9
  other platform npm skips it and `@gjsify/node-gi` falls back to a system/Homebrew
10
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/`.
11
+ runner ([`../scripts/build-gtk-runtime-darwin.mjs`](../scripts/build-gtk-runtime-darwin.mjs))
12
+ and shipped via the package tarball / staged into node-gi's
13
+ `prebuilds/darwin-arm64/gtk/`.
14
+
15
+ The Intel sibling [`@gjsify/gtk-runtime-darwin-x64`](../gtk-runtime-darwin-x64)
16
+ shares that ONE builder: nothing in the relocation pass is arch-dependent, so the
17
+ target is derived from the running Node (`darwin-${process.arch}`) and `--out` is
18
+ cross-checked against the destination package's own `os`/`cpu`. Neither package
19
+ ships a copy of the script; `gtk/manifest.json` records its repo path under
20
+ `builder` so a consumer holding only the tarball can find the recipe.
13
21
 
14
22
  ## Layout
15
23
 
@@ -22,8 +30,8 @@ gtk/
22
30
 
23
31
  ## How it is built (the relocation, the crux)
24
32
 
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):
33
+ `node ../scripts/build-gtk-runtime-darwin.mjs` runs on a macOS runner **with** a
34
+ build-time Homebrew GTK stack (the closure *source*, not shipped):
27
35
 
28
36
  1. **Collect** — walk the dylib graph with `otool -L`, recursively, from the
29
37
  typelib-backing libraries the display-free conformance loads
@@ -34,13 +42,20 @@ Homebrew GTK stack (the closure *source*, not shipped):
34
42
  sibling reference to `@loader_path/<leaf>` with `install_name_tool`, then
35
43
  **ad-hoc re-sign** it (`codesign --force --sign -`). Re-signing is mandatory
36
44
  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.
45
+ refuses to load a mis-signed dylib. On Intel the signature is not enforced, but
46
+ re-signing keeps ONE code path.
47
+ 3. **Verify** — re-read every relocated image and fail on any absolute reference to
48
+ a library the bundle *does* carry. The predicate is `$(brew --prefix)`-**derived**,
49
+ not the literal `/opt/homebrew`: that literal is vacuously absent on an Intel
50
+ runner (prefix `/usr/local`), so a hardcoded grep would have passed while proving
51
+ nothing. OS-provided libraries the bundle deliberately leaves alone are reported,
52
+ never failed.
53
+ 4. **Typelibs** — copy the typelib set into `gtk/girepository-1.0`.
54
+ 5. **Addon (optional, `--addon`)** — relocate a *copy* of the node-gi addon
55
+ (`node_gi.node`): rewrite its Homebrew-prefix refs to `@rpath/<leaf>` and add
56
+ `@loader_path/gtk/lib` as an rpath, so the addon loads the **bundled**
57
+ `libgirepository` with no Homebrew. This is the env-free path, and it gets the
58
+ same verification as the dylibs.
44
59
 
45
60
  ## How node-gi finds it
46
61
 
@@ -73,18 +88,20 @@ batteries-included install, and nothing elsewhere.
73
88
 
74
89
  ## Release flow — how the `gtk/` bundle reaches npm
75
90
 
76
- The `gtk/` payload is gitignored and built ONLY on macOS/arm64, so it cannot be
77
- produced by the main (ubuntu) release publish. A dedicated macOS job in
91
+ The `gtk/` payload is gitignored and built ONLY on macOS, so it cannot be produced
92
+ by the main (ubuntu) release publish. A dedicated macOS job in
78
93
  [`.github/workflows/release.yml`](../../../.github/workflows/release.yml) —
79
- `publish-gtk-runtime-darwin-arm64` — owns this package's publish end to end:
94
+ `publish-gtk-runtime-darwin`, a two-leg matrix over `arm64` (`macos-latest`) and
95
+ `x64` (`macos-15-intel`) — owns both darwin packages' publish end to end:
80
96
 
81
97
  1. On `release: published` it checks out the tag (the version `@release-it/bumper`
82
98
  already bumped in this package's `package.json`, in lockstep with the train).
83
99
  2. `brew install`s the GTK/GI stack as the build-time closure **source**.
84
- 3. Runs `scripts/build-gtk-runtime.mjs --out gtk` to populate `gtk/` (relocated
85
- dylibs + typelibs + `manifest.json`), then asserts no `/opt/homebrew` refs
86
- survived and that `gtk/lib` + `gtk/girepository-1.0` exist (the two dirs
87
- [`index.js`](./index.js)'s `isPresent` gate checks).
100
+ 3. Runs `../scripts/build-gtk-runtime-darwin.mjs --out gtk` to populate `gtk/`
101
+ (relocated dylibs + typelibs + `manifest.json`) — the script itself fails on a
102
+ surviving Homebrew-prefix reference — then asserts that `gtk/lib` +
103
+ `gtk/girepository-1.0` exist (the two dirs [`index.js`](./index.js)'s `isPresent`
104
+ gate checks).
88
105
  4. OIDC-publishes **only this package** (Trusted Publisher configured for
89
106
  `release.yml`), so `files: ["gtk"]` ships the whole bundle recursively (the
90
107
  gjsify packer expands a plain-directory `files` entry). Consumers then see
@@ -98,9 +115,12 @@ flags of the build script are a *separate* node-gi concern (see
98
115
  [`node-gi.yml`](../../../.github/workflows/node-gi.yml) → `macos-gtk-runtime`) and
99
116
  are **not** part of this tarball.
100
117
 
101
- **This is the PATTERN the future Windows sibling `@gjsify/gtk-runtime-win32-x64`
102
- will mirror**: a `windows-latest` publish job using the gvsbuild GTK stack as its
103
- closure source and its own MSVC-ABI relocation step, publishing only that package.
118
+ This was the PATTERN the two siblings now follow:
119
+ [`@gjsify/gtk-runtime-win32-x64`](../gtk-runtime-win32-x64) has its own
120
+ `windows-latest` publish job (gvsbuild closure source, no relocation step — Windows
121
+ resolves DLLs by search path), and
122
+ [`@gjsify/gtk-runtime-darwin-x64`](../gtk-runtime-darwin-x64) is a second leg of
123
+ *this* job, since it shares this package's builder outright.
104
124
 
105
125
  ## Scope & what a full windowing bundle still needs
106
126
 
package/gtk/manifest.json CHANGED
@@ -1,6 +1,7 @@
1
1
  {
2
2
  "platform": "darwin-arm64",
3
3
  "windowing": false,
4
+ "builder": "packages/node-gi/scripts/build-gtk-runtime-darwin.mjs",
4
5
  "generatedFrom": "/opt/homebrew",
5
6
  "dylibs": 45,
6
7
  "typelibs": 38,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjsify/gtk-runtime-darwin-arm64",
3
- "version": "0.26.1",
3
+ "version": "0.27.0",
4
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
5
  "type": "module",
6
6
  "license": "MIT",
@@ -23,7 +23,6 @@
23
23
  "files": [
24
24
  "index.js",
25
25
  "index.d.ts",
26
- "scripts",
27
26
  "gtk"
28
27
  ],
29
28
  "gjsify": {
@@ -36,7 +35,7 @@
36
35
  "tier": 3
37
36
  },
38
37
  "scripts": {
39
- "build:bundle": "node scripts/build-gtk-runtime.mjs"
38
+ "build:bundle": "node ../scripts/build-gtk-runtime-darwin.mjs"
40
39
  },
41
40
  "repository": {
42
41
  "type": "git",
@@ -1,341 +0,0 @@
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
- // WINDOWING superset (opt-in via --windowing): also bundle libadwaita, whose dylib
78
- // backs the Adw-1 typelib, AND libgtksourceview-5, whose dylib backs the GtkSource-5
79
- // typelib. The display-free conformance closure never touches Adwaita OR GtkSource,
80
- // so they are NOT base seeds — but the batteries-included bundle ships their TYPELIBS,
81
- // so without the dylibs a real `new Adw.Application()` / `new GtkSource.View()` fails
82
- // with "Failed to load shared library '…'" (→ the type is not a constructible
83
- // GObject). libgtksourceview backs the Learn6502 editor (a GtkSource.View subclass) —
84
- // the app-gnome node-gi port. This is exactly what the macOS GTK-GUI proof
85
- // (macos-gtk-windowing) needs; the recursive otool walk pulls each seed's transitive
86
- // deps + relocates them like any other seed. Mirrors the win32 --windowing superset
87
- // (WINDOWING_SEED_PATTERNS there). NB: GtkSource's runtime DATA (language-specs /
88
- // styles under share/gtksourceview-5) is a separate concern; the darwin bundle has no
89
- // share/ DATA step yet, so GtkSource CONSTRUCTS here but syntax-highlighting language
90
- // files are not bundled (tracked follow-up; the win32 bundle DOES ship them).
91
- const WINDOWING = process.argv.includes('--windowing');
92
- const WINDOWING_SEED_PATTERNS = [/^libadwaita-1\..*\.dylib$/, /^libgtksourceview-5\..*\.dylib$/];
93
-
94
- function isSystemPath(p) {
95
- return p.startsWith('/usr/lib/') || p.startsWith('/System/');
96
- }
97
-
98
- // Parse an `otool -L <lib>` output into the list of dependency install paths
99
- // (skipping the first line — the library's own id — and system libraries).
100
- function otoolDeps(libPath) {
101
- let out;
102
- try {
103
- out = sh('otool', ['-L', libPath]);
104
- } catch {
105
- return [];
106
- }
107
- const lines = out.split('\n').slice(1); // line 0 is the id, not a dep
108
- const deps = [];
109
- for (const line of lines) {
110
- const trimmed = line.trim();
111
- if (!trimmed) continue;
112
- const path = trimmed.split(' (')[0];
113
- if (!path || isSystemPath(path)) continue;
114
- deps.push(path);
115
- }
116
- return deps;
117
- }
118
-
119
- // Resolve a dependency's leaf to the real Homebrew file (following symlinks),
120
- // or null when it is not a Homebrew library we bundle.
121
- function resolveInBrew(leaf) {
122
- const candidate = join(brewLib, leaf);
123
- if (!existsSync(candidate)) return null;
124
- try {
125
- return realpathSync(candidate);
126
- } catch {
127
- return null;
128
- }
129
- }
130
-
131
- // --- 1. discover the closure ----------------------------------------------
132
- console.log(`build-gtk-runtime: brew prefix ${brewPrefix}${WINDOWING ? ' (windowing superset — + libadwaita + libgtksourceview)' : ' (display-free)'}`);
133
- const seedPatterns = WINDOWING ? [...SEED_PATTERNS, ...WINDOWING_SEED_PATTERNS] : SEED_PATTERNS;
134
- const seeds = readdirSync(brewLib).filter((f) => seedPatterns.some((re) => re.test(f)));
135
- if (seeds.length === 0) {
136
- console.error('build-gtk-runtime: no seed libraries found — is the GTK stack installed?');
137
- process.exit(1);
138
- }
139
- console.log(`build-gtk-runtime: ${seeds.length} seed libraries: ${seeds.join(', ')}`);
140
-
141
- const bundled = new Map(); // leaf -> real source path
142
- const queue = [...seeds];
143
- while (queue.length) {
144
- const leaf = queue.shift();
145
- if (bundled.has(leaf)) continue;
146
- const real = resolveInBrew(leaf);
147
- if (!real) continue; // system / non-brew dep — leave as-is
148
- bundled.set(leaf, real);
149
- for (const dep of otoolDeps(real)) {
150
- const depLeaf = basename(dep);
151
- if (!bundled.has(depLeaf)) queue.push(depLeaf);
152
- }
153
- }
154
- console.log(`build-gtk-runtime: closure = ${bundled.size} dylibs`);
155
-
156
- // --- 2. copy + relocate + re-sign -----------------------------------------
157
- const libOut = join(OUT, 'lib');
158
- rmSync(OUT, { recursive: true, force: true });
159
- mkdirSync(libOut, { recursive: true });
160
-
161
- for (const [leaf, src] of bundled) {
162
- copyFileSync(src, join(libOut, leaf));
163
- }
164
-
165
- const bundledLeaves = new Set(bundled.keys());
166
- function relocate(libPath, { id } = {}) {
167
- // Own id (dylibs only).
168
- if (id) execFileSync('install_name_tool', ['-id', `@loader_path/${basename(libPath)}`, libPath]);
169
- // Rewrite every dependency that points at a library WE bundle → @loader_path.
170
- for (const dep of otoolDeps(libPath)) {
171
- if (bundledLeaves.has(basename(dep))) {
172
- execFileSync('install_name_tool', ['-change', dep, `@loader_path/${basename(dep)}`, libPath]);
173
- }
174
- }
175
- // Ad-hoc re-sign — install_name_tool invalidated the signature (arm64 hard req).
176
- execFileSync('codesign', ['--force', '--sign', '-', libPath]);
177
- }
178
-
179
- for (const leaf of bundledLeaves) {
180
- relocate(join(libOut, leaf), { id: true });
181
- }
182
- console.log(`build-gtk-runtime: relocated + re-signed ${bundledLeaves.size} dylibs → @loader_path`);
183
-
184
- // --- 3. typelibs -----------------------------------------------------------
185
- const typelibOut = join(OUT, 'girepository-1.0');
186
- mkdirSync(typelibOut, { recursive: true });
187
- let typelibCount = 0;
188
- if (existsSync(brewTypelibs)) {
189
- for (const f of readdirSync(brewTypelibs)) {
190
- if (f.endsWith('.typelib')) {
191
- copyFileSync(join(brewTypelibs, f), join(typelibOut, f));
192
- typelibCount++;
193
- }
194
- }
195
- }
196
- console.log(`build-gtk-runtime: copied ${typelibCount} typelibs`);
197
-
198
- // --- 3b. WINDOWING data (schemas / icons / gtksource) ---------------------
199
- // The runtime DATA a REAL app needs beyond the dylibs+typelibs: compiled GSettings
200
- // schemas (Gio.Settings — a HARD startup blocker without them), the Adwaita/hicolor
201
- // icon themes, and GtkSource's language-specs/styles. These are plain files (no dylib
202
- // relocation), located at runtime via GSETTINGS_SCHEMA_DIR / XDG_DATA_DIRS (node-gi's
203
- // gtk-runtime.js maybeWireGtkWindowingEnv keys on the gschemas.compiled marker). Gated
204
- // on --windowing; the display-free bundle is byte-unchanged. NB the gdk-pixbuf image
205
- // LOADERS (needed to render SVG symbolic icons) are NOT bundled yet — they are dylibs
206
- // that need @loader_path relocation from a NESTED dir (unlike win32's plain DLL copy);
207
- // tracked follow-up, so symbolic icons may be blank until then.
208
- const windowing = { schemas: false, iconThemes: [], gtksource: false };
209
- if (WINDOWING) {
210
- const brewShare = join(brewPrefix, 'share');
211
- const findTool = (leaf) => {
212
- const inBrew = join(brewPrefix, 'bin', leaf);
213
- return existsSync(inBrew) ? inBrew : leaf; // fall back to PATH
214
- };
215
-
216
- // 3b-a. Compiled GSettings schemas + (re)compile gschemas.compiled — also the
217
- // windowing-data marker node-gi's loader detects.
218
- const schemasSrc = join(brewShare, 'glib-2.0', 'schemas');
219
- if (existsSync(schemasSrc)) {
220
- const schemasOut = join(OUT, 'share', 'glib-2.0', 'schemas');
221
- mkdirSync(schemasOut, { recursive: true });
222
- for (const f of readdirSync(schemasSrc)) {
223
- if (f.endsWith('.xml') || f.endsWith('.gschema.override') || f === 'gschemas.compiled') {
224
- copyFileSync(join(schemasSrc, f), join(schemasOut, f));
225
- }
226
- }
227
- try {
228
- execFileSync(findTool('glib-compile-schemas'), [schemasOut]);
229
- } catch (err) {
230
- console.warn(`build-gtk-runtime: WARNING — glib-compile-schemas failed: ${err?.message ?? err}`);
231
- }
232
- windowing.schemas = existsSync(join(schemasOut, 'gschemas.compiled'));
233
- console.log(`build-gtk-runtime: GSettings schemas ${windowing.schemas ? 'compiled' : 'copied (no gschemas.compiled!)'}`);
234
- } else {
235
- console.warn(`build-gtk-runtime: WARNING — ${schemasSrc} missing; GSettings schemas NOT bundled (Gio.Settings will fail)`);
236
- }
237
-
238
- // 3b-b. Icon themes (Adwaita symbolic + hicolor) + caches, loaded from
239
- // XDG_DATA_DIRS/icons/<theme>/.
240
- const updateIconCache = findTool('gtk4-update-icon-cache');
241
- for (const theme of ['Adwaita', 'hicolor']) {
242
- const themeSrc = join(brewShare, 'icons', theme);
243
- if (!existsSync(themeSrc)) continue;
244
- cpSync(themeSrc, join(OUT, 'share', 'icons', theme), { recursive: true });
245
- try {
246
- execFileSync(updateIconCache, ['-q', '-t', '-f', join(OUT, 'share', 'icons', theme)]);
247
- } catch {
248
- // an existing icon-theme.cache from the copy is a usable fallback
249
- }
250
- windowing.iconThemes.push(theme);
251
- }
252
- console.log(
253
- windowing.iconThemes.length
254
- ? `build-gtk-runtime: icon themes ${windowing.iconThemes.join(', ')}`
255
- : 'build-gtk-runtime: WARNING — no Adwaita/hicolor icon theme under share/icons',
256
- );
257
-
258
- // 3b-c. GtkSource-5 language-specs + styles (the editor's syntax highlighting).
259
- const gtksourceSrc = join(brewShare, 'gtksourceview-5');
260
- if (existsSync(gtksourceSrc)) {
261
- let copied = 0;
262
- for (const sub of ['language-specs', 'styles']) {
263
- const subSrc = join(gtksourceSrc, sub);
264
- if (existsSync(subSrc)) {
265
- cpSync(subSrc, join(OUT, 'share', 'gtksourceview-5', sub), { recursive: true });
266
- copied += readdirSync(subSrc).length;
267
- }
268
- }
269
- windowing.gtksource = copied > 0;
270
- console.log(`build-gtk-runtime: GtkSource-5 data ${windowing.gtksource ? `bundled (${copied} files)` : 'empty'}`);
271
- } else {
272
- console.warn(`build-gtk-runtime: WARNING — ${gtksourceSrc} missing; GtkSource-5 data NOT bundled`);
273
- }
274
- }
275
-
276
- // --- 4. optional: relocate a copy of the node-gi addon --------------------
277
- // The addon (built against Homebrew) carries absolute /opt/homebrew refs. Rewrite
278
- // them to @rpath/<leaf> + add an rpath to the SIBLING bundle so it loads the
279
- // bundled libgirepository with NO Homebrew — the env-free core-conformance path.
280
- if (ADDON) {
281
- if (!existsSync(ADDON)) {
282
- console.error(`build-gtk-runtime: --addon ${ADDON} not found`);
283
- process.exit(1);
284
- }
285
- const stageDir = STAGE ?? join(OUT, '..', 'staged');
286
- mkdirSync(stageDir, { recursive: true });
287
- const addonDest = join(stageDir, 'node_gi.node');
288
- copyFileSync(ADDON, addonDest);
289
- // Point the addon at the bundle staged as its sibling `gtk/`.
290
- cpSync(OUT, join(stageDir, 'gtk'), { recursive: true });
291
-
292
- for (const dep of otoolDeps(addonDest)) {
293
- // Any non-system dep (Homebrew absolute path OR @rpath leaf we bundle).
294
- if (bundledLeaves.has(basename(dep)) || dep.startsWith(brewPrefix) || dep.startsWith('/usr/local/')) {
295
- execFileSync('install_name_tool', ['-change', dep, `@rpath/${basename(dep)}`, addonDest]);
296
- }
297
- }
298
- try {
299
- execFileSync('install_name_tool', ['-add_rpath', '@loader_path/gtk/lib', addonDest]);
300
- } catch {
301
- // rpath already present — fine.
302
- }
303
- execFileSync('codesign', ['--force', '--sign', '-', addonDest]);
304
- console.log(`build-gtk-runtime: relocated addon → ${addonDest} (rpath @loader_path/gtk/lib)`);
305
- }
306
-
307
- // --- manifest + size -------------------------------------------------------
308
- function dirSize(dir) {
309
- let total = 0;
310
- for (const f of readdirSync(dir, { withFileTypes: true })) {
311
- const p = join(dir, f.name);
312
- total += f.isDirectory() ? dirSize(p) : statSync(p).size;
313
- }
314
- return total;
315
- }
316
- const libBytes = dirSize(libOut);
317
- const typelibBytes = dirSize(typelibOut);
318
- const shareOut = join(OUT, 'share');
319
- const dataBytes = WINDOWING && existsSync(shareOut) ? dirSize(shareOut) : 0;
320
- const manifest = {
321
- platform: 'darwin-arm64',
322
- windowing: WINDOWING,
323
- generatedFrom: brewPrefix,
324
- dylibs: bundledLeaves.size,
325
- typelibs: typelibCount,
326
- libBytes,
327
- typelibBytes,
328
- dataBytes,
329
- totalBytes: libBytes + typelibBytes + dataBytes,
330
- dylibList: [...bundledLeaves].sort(),
331
- ...(WINDOWING ? { windowingData: windowing } : {}),
332
- };
333
- writeFileSync(join(OUT, 'manifest.json'), JSON.stringify(manifest, null, 2));
334
-
335
- const mb = (n) => (n / 1024 / 1024).toFixed(1);
336
- console.log(
337
- `build-gtk-runtime: DONE → ${OUT}\n` +
338
- ` dylibs: ${bundledLeaves.size} (${mb(libBytes)} MiB)\n` +
339
- ` typelibs: ${typelibCount} (${mb(typelibBytes)} MiB)\n` +
340
- ` total: ${mb(manifest.totalBytes)} MiB`,
341
- );