Skip to content
Open
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -50,3 +50,6 @@ config.local.json

# 个人/本机配置(local 作用域),不进版本库
profiles/cocos.config.json

# performance profiling artifacts
perf/
2 changes: 1 addition & 1 deletion .vscodeignore
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ docs/dev/**
# ===========================================
# 工作流和脚本
# ===========================================
workflow/!(postinstall|utils|electron-rebuild).js
workflow/!(postinstall|utils|electron-rebuild|node-gyp-rebuild).js

# ===========================================
# 发布和构建产物
Expand Down
65 changes: 65 additions & 0 deletions docs/dev/native-modules-injection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Native Module .node Injection (gl / sharp ABI Adaptation for Host Electron)

## Background

cocos-cli depends on two native modules:

- `gl` (headless-gl): loads `build/Release/webgl.node` via `bindings('webgl')`
- `sharp`: loads its native binding via `require('../build/Release/sharp-<platformAndArch>.node')`

These `.node` binaries are tightly coupled to the ABI of the Node/Electron runtime that
loads them. When a cocos-cli process is started by a host Electron runtime (e.g. pink /
Cocos Creator editor), the `.node` binaries bundled with cocos-cli may not match the
host's Electron version, causing load failures.

## Mechanism

Via patch-package patches (`patches/gl+9.0.0-rc.10.patch`, `patches/sharp+0.32.6.patch`),
the gl / sharp native binding loaders read environment variables. When an env var is
set, the `.node` file at the specified path is loaded; otherwise the bundled default
binary is used. **When unset, behavior is identical to before.**

## Env Var Contract (host side)

| Env var | Meaning | Example value |
|---|---|---|
| `COCOS_CLI_GL_NODE` | Path to the gl (webgl) native binding `.node` | `/path/to/electron/webgl.node` |
| `COCOS_CLI_SHARP_NODE` | Path to the sharp native binding `.node` | `/path/to/electron/sharp-darwin-arm64v8.node` |

> The path should be absolute. When unset or empty, cocos-cli falls back to its bundled `.node`.

### pink CocosMainService usage example

Pass the env vars when spawning the cocos-cli process:

```ts
const child = spawn(cocosCliEntry, args, {
env: {
...process.env,
COCOS_CLI_GL_NODE: path.join(pinkNativeDir, 'webgl.node'),
COCOS_CLI_SHARP_NODE: path.join(pinkNativeDir, 'sharp-darwin-arm64v8.node'),
},
});
```

## Child Process Propagation

cocos-cli's internal child processes (effect compilation, builder workers, scene process,
script/engine compilation, etc.) are launched via `spawn`/`fork` without an explicit `env`
override, so they **automatically inherit** the main process env vars. The host only needs
to set the env vars on the main process to cover the whole chain.

## Patch Maintenance

- Patch files: `patches/gl+9.0.0-rc.10.patch`, `patches/sharp+0.32.6.patch`
- Apply/rebuild: `npm run rebuild` (runs patch-package + @electron/rebuild)
- Apply manually: `npx patch-package`
- Regenerate patches (after modifying node_modules):

```sh
npx patch-package gl sharp --exclude 'build/|node-addon-api'
```

> Note: passing multiple `--exclude` values merges them into a single regex (with a
> literal comma), so use an alternation inside one regex (e.g. `'build/|node-addon-api'`);
> paths are relative to the package root (no leading `/`), so `build/` matches `build/...`.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"start:preview": "node ./dist/cli.js preview --scene-editor",
"start:mcp-inspector": "npx @modelcontextprotocol/inspector",
"cli": "node ./dist/cli.js",
"rebuild": "node workflow/electron-rebuild.js"
"rebuild": "node workflow/electron-rebuild.js",
"rebuild:node-gyp": "node workflow/node-gyp-rebuild.js"
},
"author": "COCOS",
"files": [
Expand Down Expand Up @@ -175,7 +176,7 @@
"rollup-plugin-terser": "^7.0.2",
"rotating-file-stream": "^3.2.7",
"semver": "^7.7.2",
"sharp": "^0.32.6",
"sharp": "0.32.6",
"socket.io": "^4.8.1",
"socket.io-client": "^4.8.1",
"strip-ansi": "^6.0.1",
Expand All @@ -193,4 +194,4 @@
"zod-to-ts": "1.1.4",
"xml2js": "^0.6.0"
}
}
}
14 changes: 14 additions & 0 deletions patches/gl+9.0.0-rc.10.patch
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
diff --git a/node_modules/gl/src/javascript/native-gl.js b/node_modules/gl/src/javascript/native-gl.js
index 996ee1d..8c41e57 100644
--- a/node_modules/gl/src/javascript/native-gl.js
+++ b/node_modules/gl/src/javascript/native-gl.js
@@ -1,3 +1,8 @@
-const NativeWebGL = require('bindings')('webgl')
+// Allow overriding the native .node binding via env var (used when the host electron ABI differs from the default node ABI)
+const nativeWebglPath = process.env.COCOS_CLI_GL_NODE;
+if (nativeWebglPath) {
+ console.log(`[cocos-cli] gl: loading native binding from COCOS_CLI_GL_NODE=${nativeWebglPath}`);
+}
+const NativeWebGL = nativeWebglPath ? require(nativeWebglPath) : require('bindings')('webgl')
const { WebGLRenderingContext: NativeWebGLRenderingContext } = NativeWebGL
process.on('exit', NativeWebGL.cleanup)
diff --git a/node_modules/gl/src/native/webgl.cc b/node_modules/gl/src/native/webgl.cc
index 0e03023..bd406a7 100644
--- a/node_modules/gl/src/native/webgl.cc
Expand Down
17 changes: 17 additions & 0 deletions patches/sharp+0.32.6.patch
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
diff --git a/node_modules/sharp/lib/sharp.js b/node_modules/sharp/lib/sharp.js
index a41e83d..f1f48b1 100644
--- a/node_modules/sharp/lib/sharp.js
+++ b/node_modules/sharp/lib/sharp.js
@@ -7,6 +7,11 @@ const platformAndArch = require('./platform')();
/* istanbul ignore next */
try {
- module.exports = require(`../build/Release/sharp-${platformAndArch}.node`);
+ // Allow overriding the native .node binding via env var (used when the host electron ABI differs from the default node ABI)
+ const nativeBindingPath = process.env.COCOS_CLI_SHARP_NODE || `../build/Release/sharp-${platformAndArch}.node`;
+ if (process.env.COCOS_CLI_SHARP_NODE) {
+ console.log(`[cocos-cli] sharp: loading native binding from COCOS_CLI_SHARP_NODE=${process.env.COCOS_CLI_SHARP_NODE}`);
+ }
+ module.exports = require(nativeBindingPath);
} catch (err) {
// Bail early if bindings aren't available
const help = ['', 'Something went wrong installing the "sharp" module', '', err.message, '', 'Possible solutions:'];
4 changes: 2 additions & 2 deletions workflow/electron-rebuild.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@ function run(cmd) {
}

try {
run('npx --yes patch-package');
run('npx --yes patch-package --error-on-fail');
run(`npx @electron/rebuild --force --version ${electronVersion}`);
} catch (err) {
console.error('\n[rebuild] failed');
process.exit(1);
}
}
14 changes: 14 additions & 0 deletions workflow/node-gyp-rebuild.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const { execSync } = require('child_process');

function run(cmd) {
console.log(`\n> ${cmd}`);
execSync(cmd, { stdio: 'inherit' });
}

try {
run('npx --yes patch-package --error-on-fail');
run('npm rebuild');
} catch (err) {
console.error('\n[rebuild] failed');
process.exit(1);
}
2 changes: 2 additions & 0 deletions workflow/release.js
Original file line number Diff line number Diff line change
Expand Up @@ -474,6 +474,8 @@ function createReleasePipeline(config) {
const rebuild = async () => {
if (config.type === 'electron') {
await runCommand('npm', ['run', 'rebuild'], { cwd: extensionDir() });
} else {
await runCommand('npm', ['run', 'rebuild:node-gyp'], { cwd: extensionDir() });
}
};

Expand Down
Loading