mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
doc,test: add documentation and test on how to use addons in SEA
PR-URL: https://github.com/nodejs/node/pull/59582 Fixes: https://github.com/nodejs/node/issues/60611 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Darshan Sen <raisinten@gmail.com> Reviewed-By: Rafael Gonzaga <rafael.nunu@hotmail.com> Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
This commit is contained in:
1 parent
8131d386cc
commit
61cf89cb35
5 files changed
+150
No files matched your search
@@ -476,6 +476,43 @@ are equal to [`process.execPath`][].
|
||||
The value of `__dirname` in the injected main script is equal to the directory
|
||||
name of [`process.execPath`][].
|
||||
|
||||
### Using native addons in the injected main script
|
||||
|
||||
Native addons can be bundled as assets into the single-executable application
|
||||
by specifying them in the `assets` field of the configuration file used to
|
||||
generate the single-executable application preparation blob.
|
||||
The addon can then be loaded in the injected main script by writing the asset
|
||||
to a temporary file and loading it with `process.dlopen()`.
|
||||
|
||||
```json
|
||||
{
|
||||
"main": "/path/to/bundled/script.js",
|
||||
"output": "/path/to/write/the/generated/blob.blob",
|
||||
"assets": {
|
||||
"myaddon.node": "/path/to/myaddon/build/Release/myaddon.node"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```js
|
||||
// script.js
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { getRawAsset } = require('node:sea');
|
||||
const addonPath = path.join(os.tmpdir(), 'myaddon.node');
|
||||
fs.writeFileSync(addonPath, new Uint8Array(getRawAsset('myaddon.node')));
|
||||
const myaddon = { exports: {} };
|
||||
process.dlopen(myaddon, addonPath);
|
||||
console.log(myaddon.exports);
|
||||
fs.rmSync(addonPath);
|
||||
```
|
||||
|
||||
Known caveat: if the single-executable application is produced by postject running on a Linux arm64 docker container,
|
||||
[the produced ELF binary does not have the correct hash table to load the addons][postject-linux-arm64-issue] and
|
||||
will crash on `process.dlopen()`. Build the single-executable application on other platforms, or at least on
|
||||
a non-container Linux arm64 environment to work around this issue.
|
||||
|
||||
## Notes
|
||||
|
||||
### Single executable application creation process
|
||||
@@ -528,6 +565,7 @@ to help us document them.
|
||||
[documentation about startup snapshot support in Node.js]: cli.md#--build-snapshot
|
||||
[fuse]: https://www.electronjs.org/docs/latest/tutorial/fuses
|
||||
[postject]: https://github.com/nodejs/postject
|
||||
[postject-linux-arm64-issue]: https://github.com/nodejs/postject/issues/105
|
||||
[signtool]: https://learn.microsoft.com/en-us/windows/win32/seccrypto/signtool
|
||||
[single executable applications]: https://github.com/nodejs/single-executable
|
||||
[supported by Node.js]: https://github.com/nodejs/node/blob/main/BUILDING.md#platform-list
|
||||
@@ -12,3 +12,15 @@ prefix node-api
|
||||
# https://github.com/nodejs/node/issues/43457
|
||||
test_fatal/test_threads: PASS,FLAKY
|
||||
test_fatal/test_threads_report: PASS,FLAKY
|
||||
|
||||
[$system==linux && $arch==ppc64]
|
||||
# https://github.com/nodejs/node/issues/59561
|
||||
test_sea_addon/test: SKIP
|
||||
|
||||
[$system==linux && $arch==arm64]
|
||||
# https://github.com/nodejs/postject/issues/105
|
||||
test_sea_addon/test: SKIP
|
||||
|
||||
[$system==macos && $arch==x64]
|
||||
# https://github.com/nodejs/node/issues/59553
|
||||
test_sea_addon/test: SKIP
|
||||
@@ -0,0 +1,17 @@
|
||||
#include <node_api.h>
|
||||
#include <string.h>
|
||||
#include "../../js-native-api/common.h"
|
||||
|
||||
static napi_value Method(napi_env env, napi_callback_info info) {
|
||||
napi_value world;
|
||||
const char* str = "world";
|
||||
size_t str_len = strlen(str);
|
||||
NODE_API_CALL(env, napi_create_string_utf8(env, str, str_len, &world));
|
||||
return world;
|
||||
}
|
||||
|
||||
NAPI_MODULE_INIT() {
|
||||
napi_property_descriptor desc = DECLARE_NODE_API_PROPERTY("hello", Method);
|
||||
NODE_API_CALL(env, napi_define_properties(env, exports, 1, &desc));
|
||||
return exports;
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"targets": [
|
||||
{
|
||||
"target_name": "binding",
|
||||
"sources": [ "binding.c" ]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
'use strict';
|
||||
// This tests that SEA can load addons packaged as assets by writing them to disk
|
||||
// and loading them via process.dlopen().
|
||||
const common = require('../../common');
|
||||
const { generateSEA, skipIfSingleExecutableIsNotSupported } = require('../../common/sea');
|
||||
|
||||
skipIfSingleExecutableIsNotSupported();
|
||||
|
||||
const assert = require('assert');
|
||||
|
||||
const tmpdir = require('../../common/tmpdir');
|
||||
const { copyFileSync, writeFileSync, existsSync, rmSync } = require('fs');
|
||||
const {
|
||||
spawnSyncAndExitWithoutError,
|
||||
spawnSyncAndAssert,
|
||||
} = require('../../common/child_process');
|
||||
const { join } = require('path');
|
||||
const configFile = tmpdir.resolve('sea-config.json');
|
||||
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
|
||||
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
|
||||
tmpdir.refresh();
|
||||
|
||||
// Copy test fixture to working directory
|
||||
const addonPath = join(__dirname, 'build', common.buildType, 'binding.node');
|
||||
const copiedAddonPath = tmpdir.resolve('binding.node');
|
||||
copyFileSync(addonPath, copiedAddonPath);
|
||||
writeFileSync(tmpdir.resolve('sea.js'), `
|
||||
const sea = require('node:sea');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const addonPath = path.join(${JSON.stringify(tmpdir.path)}, 'hello.node');
|
||||
fs.writeFileSync(addonPath, new Uint8Array(sea.getRawAsset('hello.node')));
|
||||
const mod = {exports: {}}
|
||||
process.dlopen(mod, addonPath);
|
||||
console.log('hello,', mod.exports.hello());
|
||||
`, 'utf-8');
|
||||
|
||||
writeFileSync(configFile, `
|
||||
{
|
||||
"main": "sea.js",
|
||||
"output": "sea-prep.blob",
|
||||
"disableExperimentalSEAWarning": true,
|
||||
"assets": {
|
||||
"hello.node": "binding.node"
|
||||
}
|
||||
}
|
||||
`, 'utf8');
|
||||
|
||||
spawnSyncAndExitWithoutError(
|
||||
process.execPath,
|
||||
['--experimental-sea-config', 'sea-config.json'],
|
||||
{ cwd: tmpdir.path },
|
||||
);
|
||||
assert(existsSync(seaPrepBlob));
|
||||
|
||||
generateSEA(outputFile, process.execPath, seaPrepBlob);
|
||||
|
||||
// Remove the copied addon after it's been packaged into the SEA blob
|
||||
rmSync(copiedAddonPath, { force: true });
|
||||
|
||||
spawnSyncAndAssert(
|
||||
outputFile,
|
||||
[],
|
||||
{
|
||||
env: {
|
||||
...process.env,
|
||||
NODE_DEBUG_NATIVE: 'SEA',
|
||||
},
|
||||
cwd: tmpdir.path,
|
||||
},
|
||||
{
|
||||
stdout: /hello, world/,
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user