Skip to content

Commit 5f6f0fb

Browse files
authored
feat(workflow): support native node-gyp rebuilds (cocos#877)
1 parent eff2b35 commit 5f6f0fb

10 files changed

Lines changed: 123 additions & 7 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,3 +50,6 @@ config.local.json
5050

5151
# 个人/本机配置(local 作用域),不进版本库
5252
profiles/cocos.config.json
53+
54+
# performance profiling artifacts
55+
perf/

.vscodeignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ docs/dev/**
7272
# ===========================================
7373
# 工作流和脚本
7474
# ===========================================
75-
workflow/!(postinstall|utils|electron-rebuild).js
75+
workflow/!(postinstall|utils|electron-rebuild|node-gyp-rebuild).js
7676

7777
# ===========================================
7878
# 发布和构建产物
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
# Native Module .node Injection (gl / sharp ABI Adaptation for Host Electron)
2+
3+
## Background
4+
5+
cocos-cli depends on two native modules:
6+
7+
- `gl` (headless-gl): loads `build/Release/webgl.node` via `bindings('webgl')`
8+
- `sharp`: loads its native binding via `require('../build/Release/sharp-<platformAndArch>.node')`
9+
10+
These `.node` binaries are tightly coupled to the ABI of the Node/Electron runtime that
11+
loads them. When a cocos-cli process is started by a host Electron runtime (e.g. pink /
12+
Cocos Creator editor), the `.node` binaries bundled with cocos-cli may not match the
13+
host's Electron version, causing load failures.
14+
15+
## Mechanism
16+
17+
Via patch-package patches (`patches/gl+9.0.0-rc.10.patch`, `patches/sharp+0.32.6.patch`),
18+
the gl / sharp native binding loaders read environment variables. When an env var is
19+
set, the `.node` file at the specified path is loaded; otherwise the bundled default
20+
binary is used. **When unset, behavior is identical to before.**
21+
22+
## Env Var Contract (host side)
23+
24+
| Env var | Meaning | Example value |
25+
|---|---|---|
26+
| `COCOS_CLI_GL_NODE` | Path to the gl (webgl) native binding `.node` | `/path/to/electron/webgl.node` |
27+
| `COCOS_CLI_SHARP_NODE` | Path to the sharp native binding `.node` | `/path/to/electron/sharp-darwin-arm64v8.node` |
28+
29+
> The path should be absolute. When unset or empty, cocos-cli falls back to its bundled `.node`.
30+
31+
### pink CocosMainService usage example
32+
33+
Pass the env vars when spawning the cocos-cli process:
34+
35+
```ts
36+
const child = spawn(cocosCliEntry, args, {
37+
env: {
38+
...process.env,
39+
COCOS_CLI_GL_NODE: path.join(pinkNativeDir, 'webgl.node'),
40+
COCOS_CLI_SHARP_NODE: path.join(pinkNativeDir, 'sharp-darwin-arm64v8.node'),
41+
},
42+
});
43+
```
44+
45+
## Child Process Propagation
46+
47+
cocos-cli's internal child processes (effect compilation, builder workers, scene process,
48+
script/engine compilation, etc.) are launched via `spawn`/`fork` without an explicit `env`
49+
override, so they **automatically inherit** the main process env vars. The host only needs
50+
to set the env vars on the main process to cover the whole chain.
51+
52+
## Patch Maintenance
53+
54+
- Patch files: `patches/gl+9.0.0-rc.10.patch`, `patches/sharp+0.32.6.patch`
55+
- Apply/rebuild: `npm run rebuild` (runs patch-package + @electron/rebuild)
56+
- Apply manually: `npx patch-package`
57+
- Regenerate patches (after modifying node_modules):
58+
59+
```sh
60+
npx patch-package gl sharp --exclude 'build/|node-addon-api'
61+
```
62+
63+
> Note: passing multiple `--exclude` values merges them into a single regex (with a
64+
> literal comma), so use an alternation inside one regex (e.g. `'build/|node-addon-api'`);
65+
> paths are relative to the package root (no leading `/`), so `build/` matches `build/...`.

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
"start:preview": "node ./dist/cli.js preview --scene-editor",
5050
"start:mcp-inspector": "npx @modelcontextprotocol/inspector",
5151
"cli": "node ./dist/cli.js",
52-
"rebuild": "node workflow/electron-rebuild.js"
52+
"rebuild": "node workflow/electron-rebuild.js",
53+
"rebuild:node-gyp": "node workflow/node-gyp-rebuild.js"
5354
},
5455
"author": "COCOS",
5556
"files": [
@@ -175,7 +176,7 @@
175176
"rollup-plugin-terser": "^7.0.2",
176177
"rotating-file-stream": "^3.2.7",
177178
"semver": "^7.7.2",
178-
"sharp": "^0.32.6",
179+
"sharp": "0.32.6",
179180
"socket.io": "^4.8.1",
180181
"socket.io-client": "^4.8.1",
181182
"strip-ansi": "^6.0.1",
@@ -193,4 +194,4 @@
193194
"zod-to-ts": "1.1.4",
194195
"xml2js": "^0.6.0"
195196
}
196-
}
197+
}

patches/gl+9.0.0-rc.10.patch

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,17 @@
1+
diff --git a/node_modules/gl/src/javascript/native-gl.js b/node_modules/gl/src/javascript/native-gl.js
2+
index 996ee1d..8c41e57 100644
3+
--- a/node_modules/gl/src/javascript/native-gl.js
4+
+++ b/node_modules/gl/src/javascript/native-gl.js
5+
@@ -1,3 +1,8 @@
6+
-const NativeWebGL = require('bindings')('webgl')
7+
+// Allow overriding the native .node binding via env var (used when the host electron ABI differs from the default node ABI)
8+
+const nativeWebglPath = process.env.COCOS_CLI_GL_NODE;
9+
+if (nativeWebglPath) {
10+
+ console.log(`[cocos-cli] gl: loading native binding from COCOS_CLI_GL_NODE=${nativeWebglPath}`);
11+
+}
12+
+const NativeWebGL = nativeWebglPath ? require(nativeWebglPath) : require('bindings')('webgl')
13+
const { WebGLRenderingContext: NativeWebGLRenderingContext } = NativeWebGL
14+
process.on('exit', NativeWebGL.cleanup)
115
diff --git a/node_modules/gl/src/native/webgl.cc b/node_modules/gl/src/native/webgl.cc
216
index 0e03023..bd406a7 100644
317
--- a/node_modules/gl/src/native/webgl.cc

patches/sharp+0.32.6.patch

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
diff --git a/node_modules/sharp/lib/sharp.js b/node_modules/sharp/lib/sharp.js
2+
index a41e83d..f1f48b1 100644
3+
--- a/node_modules/sharp/lib/sharp.js
4+
+++ b/node_modules/sharp/lib/sharp.js
5+
@@ -7,6 +7,11 @@ const platformAndArch = require('./platform')();
6+
/* istanbul ignore next */
7+
try {
8+
- module.exports = require(`../build/Release/sharp-${platformAndArch}.node`);
9+
+ // Allow overriding the native .node binding via env var (used when the host electron ABI differs from the default node ABI)
10+
+ const nativeBindingPath = process.env.COCOS_CLI_SHARP_NODE || `../build/Release/sharp-${platformAndArch}.node`;
11+
+ if (process.env.COCOS_CLI_SHARP_NODE) {
12+
+ console.log(`[cocos-cli] sharp: loading native binding from COCOS_CLI_SHARP_NODE=${process.env.COCOS_CLI_SHARP_NODE}`);
13+
+ }
14+
+ module.exports = require(nativeBindingPath);
15+
} catch (err) {
16+
// Bail early if bindings aren't available
17+
const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, '', 'Possible solutions:'];

workflow/electron-rebuild.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,9 @@ function run(cmd) {
1515
}
1616

1717
try {
18-
run('npx --yes patch-package');
18+
run('npx --yes patch-package --error-on-fail');
1919
run(`npx @electron/rebuild --force --version ${electronVersion}`);
2020
} catch (err) {
2121
console.error('\n[rebuild] failed');
2222
process.exit(1);
23-
}
23+
}

workflow/node-gyp-rebuild.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
const { execSync } = require('child_process');
2+
3+
function run(cmd) {
4+
console.log(`\n> ${cmd}`);
5+
execSync(cmd, { stdio: 'inherit' });
6+
}
7+
8+
try {
9+
run('npx --yes patch-package --error-on-fail');
10+
run('npm rebuild');
11+
} catch (err) {
12+
console.error('\n[rebuild] failed');
13+
process.exit(1);
14+
}

workflow/release.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,6 +474,8 @@ function createReleasePipeline(config) {
474474
const rebuild = async () => {
475475
if (config.type === 'electron') {
476476
await runCommand('npm', ['run', 'rebuild'], { cwd: extensionDir() });
477+
} else {
478+
await runCommand('npm', ['run', 'rebuild:node-gyp'], { cwd: extensionDir() });
477479
}
478480
};
479481

0 commit comments

Comments
 (0)