Skip to content

Commit dad1c1d

Browse files
committed
sea: support argv in sea config
implement argv config for sea including snapshot
1 parent a7fde8a commit dad1c1d

File tree

5 files changed

+281
-14
lines changed

5 files changed

+281
-14
lines changed

src/node_sea.cc

Lines changed: 92 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,18 @@ size_t SeaSerializer::Write(const SeaResource& sea) {
142142
written_total += WriteStringView(arg, StringLogMode::kAddressAndContent);
143143
}
144144
}
145+
146+
if (static_cast<bool>(sea.flags & SeaFlags::kIncludeUserArgv)) {
147+
Debug("Write SEA resource user argv size %zu\n", sea.user_argv.size());
148+
written_total += WriteArithmetic<size_t>(sea.user_argv.size());
149+
for (const auto& arg : sea.user_argv) {
150+
Debug("Write SEA resource user arg %s at %p, size=%zu\n",
151+
arg.data(),
152+
arg.data(),
153+
arg.size());
154+
written_total += WriteStringView(arg, StringLogMode::kAddressAndContent);
155+
}
156+
}
145157
return written_total;
146158
}
147159

@@ -224,13 +236,29 @@ SeaResource SeaDeserializer::Read() {
224236
exec_argv.emplace_back(arg);
225237
}
226238
}
239+
240+
std::vector<std::string_view> user_argv;
241+
if (static_cast<bool>(flags & SeaFlags::kIncludeUserArgv)) {
242+
size_t user_argv_size = ReadArithmetic<size_t>();
243+
Debug("Read SEA resource user args size %zu\n", user_argv_size);
244+
user_argv.reserve(user_argv_size);
245+
for (size_t i = 0; i < user_argv_size; ++i) {
246+
std::string_view arg = ReadStringView(StringLogMode::kAddressAndContent);
247+
Debug("Read SEA resource user arg %s at %p, size=%zu\n",
248+
arg.data(),
249+
arg.data(),
250+
arg.size());
251+
user_argv.emplace_back(arg);
252+
}
253+
}
227254
return {flags,
228-
exec_argv_extension,
229-
code_path,
230-
code,
231-
code_cache,
232-
assets,
233-
exec_argv};
255+
exec_argv_extension,
256+
code_path,
257+
code,
258+
code_cache,
259+
assets,
260+
exec_argv,
261+
user_argv};
234262
}
235263

236264
std::string_view FindSingleExecutableBlob() {
@@ -316,12 +344,14 @@ std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
316344
static std::vector<char*> new_argv;
317345
static std::vector<std::string> exec_argv_storage;
318346
static std::vector<std::string> cli_extension_args;
347+
static std::vector<std::string> user_argv_storage;
319348

320349
SeaResource sea_resource = FindSingleExecutableResource();
321350

322351
new_argv.clear();
323352
exec_argv_storage.clear();
324353
cli_extension_args.clear();
354+
user_argv_storage.clear();
325355

326356
// Handle CLI extension mode for --node-options
327357
if (sea_resource.exec_argv_extension == SeaExecArgvExtension::kCli) {
@@ -341,10 +371,11 @@ std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
341371
}
342372
}
343373

344-
// Reserve space for argv[0], exec argv, cli extension args, original argv,
345-
// and nullptr
374+
// Reserve space for two argv[0] , exec argv, cli extension args, user argv,
375+
// original argv and nullptr
346376
new_argv.reserve(argc + sea_resource.exec_argv.size() +
347-
cli_extension_args.size() + 2);
377+
cli_extension_args.size() + sea_resource.user_argv.size() +
378+
3);
348379
new_argv.emplace_back(argv[0]);
349380

350381
// Insert exec argv from SEA config
@@ -363,8 +394,18 @@ std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
363394
new_argv.emplace_back(exec_argv_storage.back().data());
364395
}
365396

366-
// Add actual run time arguments
367-
new_argv.insert(new_argv.end(), argv, argv + argc);
397+
new_argv.emplace_back(argv[0]);
398+
// Insert user argv from SEA config
399+
if (!sea_resource.user_argv.empty()) {
400+
user_argv_storage.reserve(sea_resource.user_argv.size());
401+
for (const auto& arg : sea_resource.user_argv) {
402+
user_argv_storage.emplace_back(arg);
403+
new_argv.emplace_back(user_argv_storage.back().data());
404+
}
405+
}
406+
407+
// Add actual run time arguments.
408+
new_argv.insert(new_argv.end(), argv + 1, argv + argc);
368409
new_argv.emplace_back(nullptr);
369410
argc = new_argv.size() - 1;
370411
argv = new_argv.data();
@@ -382,6 +423,7 @@ struct SeaConfig {
382423
SeaExecArgvExtension exec_argv_extension = SeaExecArgvExtension::kEnv;
383424
std::unordered_map<std::string, std::string> assets;
384425
std::vector<std::string> exec_argv;
426+
std::vector<std::string> user_argv;
385427
};
386428

387429
std::optional<SeaConfig> ParseSingleExecutableConfig(
@@ -544,6 +586,35 @@ std::optional<SeaConfig> ParseSingleExecutableConfig(
544586
config_path);
545587
return std::nullopt;
546588
}
589+
} else if (key == "argv") {
590+
simdjson::ondemand::array argv_array;
591+
FPrintF(stderr, "\"argv\" start \n");
592+
593+
if (field.value().get_array().get(argv_array)) {
594+
FPrintF(stderr,
595+
"\"argv\" field of %s is not an array of strings\n",
596+
config_path);
597+
return std::nullopt;
598+
}
599+
std::vector<std::string> user_argv;
600+
601+
for (auto argv : argv_array) {
602+
FPrintF(stderr, "\"argv\"\n");
603+
604+
std::string_view argv_str;
605+
if (argv.get_string().get(argv_str)) {
606+
FPrintF(stderr,
607+
"\"argv\" field of %s is not an array of strings\n",
608+
config_path);
609+
return std::nullopt;
610+
}
611+
FPrintF(stderr, "\"argv\" : %s \n", argv_str);
612+
user_argv.emplace_back(argv_str);
613+
}
614+
if (!user_argv.empty()) {
615+
result.flags |= SeaFlags::kIncludeUserArgv;
616+
result.user_argv = std::move(user_argv);
617+
}
547618
}
548619
}
549620

@@ -579,9 +650,11 @@ ExitCode GenerateSnapshotForSEA(const SeaConfig& config,
579650
const SnapshotConfig& snapshot_config,
580651
std::vector<char>* snapshot_blob) {
581652
SnapshotData snapshot;
582-
// TODO(joyeecheung): make the arguments configurable through the JSON
583-
// config or a programmatic API.
584653
std::vector<std::string> patched_args = {args[0], config.main_path};
654+
if (!config.user_argv.empty()) {
655+
patched_args.insert(
656+
patched_args.end(), config.user_argv.begin(),config.user_argv.end());
657+
}
585658
ExitCode exit_code = SnapshotBuilder::Generate(&snapshot,
586659
patched_args,
587660
exec_args,
@@ -741,6 +814,10 @@ ExitCode GenerateSingleExecutableBlob(
741814
for (const auto& arg : config.exec_argv) {
742815
exec_argv_view.emplace_back(arg);
743816
}
817+
std::vector<std::string_view> user_argv_view;
818+
for (const auto& arg : config.user_argv) {
819+
user_argv_view.emplace_back(arg);
820+
}
744821
SeaResource sea{
745822
config.flags,
746823
config.exec_argv_extension,
@@ -750,7 +827,8 @@ ExitCode GenerateSingleExecutableBlob(
750827
: std::string_view{main_script.data(), main_script.size()},
751828
optional_sv_code_cache,
752829
assets_view,
753-
exec_argv_view};
830+
exec_argv_view,
831+
user_argv_view};
754832

755833
SeaSerializer serializer;
756834
serializer.Write(sea);

src/node_sea.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ enum class SeaFlags : uint32_t {
2929
kUseCodeCache = 1 << 2,
3030
kIncludeAssets = 1 << 3,
3131
kIncludeExecArgv = 1 << 4,
32+
kIncludeUserArgv = 1 << 5,
3233
};
3334

3435
enum class SeaExecArgvExtension : uint8_t {
@@ -45,6 +46,7 @@ struct SeaResource {
4546
std::optional<std::string_view> code_cache;
4647
std::unordered_map<std::string_view, std::string_view> assets;
4748
std::vector<std::string_view> exec_argv;
49+
std::vector<std::string_view> user_argv;
4850

4951
bool use_snapshot() const;
5052
bool use_code_cache() const;

test/fixtures/sea-argv.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
const assert = require('assert');
2+
3+
process.emitWarning('This warning should not be shown in the output', 'TestWarning');
4+
5+
console.log('process.argv:', JSON.stringify(process.argv));
6+
7+
assert.deepStrictEqual(process.argv.slice(2), [
8+
'user-arg1',
9+
'user-arg2',
10+
'user-arg3'
11+
]);
12+
13+
console.log('multiple argv test passed');
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
const {
6+
generateSEA,
7+
skipIfSingleExecutableIsNotSupported,
8+
} = require('../common/sea');
9+
10+
skipIfSingleExecutableIsNotSupported();
11+
12+
// This tests the argv functionality with multiple arguments in single executable applications.
13+
14+
const fixtures = require('../common/fixtures');
15+
const tmpdir = require('../common/tmpdir');
16+
const { copyFileSync, writeFileSync, existsSync } = require('fs');
17+
const { spawnSyncAndAssert, spawnSyncAndExitWithoutError } = require('../common/child_process');
18+
const { join } = require('path');
19+
const assert = require('assert');
20+
21+
const configFile = tmpdir.resolve('sea-config.json');
22+
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
23+
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
24+
25+
tmpdir.refresh();
26+
27+
// Copy test fixture to working directory
28+
copyFileSync(fixtures.path('sea-argv.js'), tmpdir.resolve('sea.js'));
29+
30+
writeFileSync(configFile, `
31+
{
32+
"main": "sea.js",
33+
"output": "sea-prep.blob",
34+
"disableExperimentalSEAWarning": true,
35+
"argv": ["user-arg1", "user-arg2"]
36+
}
37+
`);
38+
39+
spawnSyncAndExitWithoutError(
40+
process.execPath,
41+
['--experimental-sea-config', 'sea-config.json'],
42+
{ cwd: tmpdir.path });
43+
44+
assert(existsSync(seaPrepBlob));
45+
46+
generateSEA(outputFile, process.execPath, seaPrepBlob);
47+
48+
// Test that multiple argv are properly applied
49+
spawnSyncAndAssert(
50+
outputFile,
51+
[
52+
'user-arg3',
53+
],
54+
{
55+
env: {
56+
...process.env,
57+
NODE_NO_WARNINGS: '0',
58+
COMMON_DIRECTORY: join(__dirname, '..', 'common'),
59+
NODE_DEBUG_NATIVE: 'SEA',
60+
}
61+
},
62+
{
63+
stdout: /multiple argv test passed/,
64+
trim: true,
65+
});
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
'use strict';
2+
3+
require('../common');
4+
5+
const {
6+
generateSEA,
7+
skipIfSingleExecutableIsNotSupported,
8+
} = require('../common/sea');
9+
10+
skipIfSingleExecutableIsNotSupported();
11+
12+
// This tests the snapshot support in single executable applications.
13+
14+
const tmpdir = require('../common/tmpdir');
15+
const { writeFileSync, existsSync } = require('fs');
16+
const {
17+
spawnSyncAndAssert,
18+
spawnSyncAndExit,
19+
} = require('../common/child_process');
20+
const assert = require('assert');
21+
22+
const configFile = tmpdir.resolve('sea-config.json');
23+
const seaPrepBlob = tmpdir.resolve('sea-prep.blob');
24+
const outputFile = tmpdir.resolve(process.platform === 'win32' ? 'sea.exe' : 'sea');
25+
26+
{
27+
tmpdir.refresh();
28+
29+
writeFileSync(tmpdir.resolve('snapshot.js'), '', 'utf-8');
30+
writeFileSync(configFile, `
31+
{
32+
"main": "snapshot.js",
33+
"output": "sea-prep.blob",
34+
"useSnapshot": true
35+
}
36+
`);
37+
38+
spawnSyncAndExit(
39+
process.execPath,
40+
['--experimental-sea-config', 'sea-config.json'],
41+
{
42+
cwd: tmpdir.path
43+
},
44+
{
45+
status: 1,
46+
signal: null,
47+
stderr: /snapshot\.js does not invoke v8\.startupSnapshot\.setDeserializeMainFunction\(\)/
48+
});
49+
}
50+
51+
{
52+
tmpdir.refresh();
53+
const code = `
54+
const {
55+
setDeserializeMainFunction,
56+
} = require('v8').startupSnapshot;
57+
58+
setDeserializeMainFunction(() => {
59+
console.log('Hello from snapshot with config argv:', process.argv[2]);
60+
});
61+
`;
62+
63+
writeFileSync(tmpdir.resolve('snapshot.js'), code, 'utf-8');
64+
writeFileSync(configFile, `
65+
{
66+
"main": "snapshot.js",
67+
"output": "sea-prep.blob",
68+
"useSnapshot": true,
69+
"argv": ["user-arg"]
70+
}
71+
`);
72+
73+
spawnSyncAndAssert(
74+
process.execPath,
75+
['--experimental-sea-config', 'sea-config.json'],
76+
{
77+
cwd: tmpdir.path,
78+
env: {
79+
NODE_DEBUG_NATIVE: 'SEA',
80+
...process.env,
81+
},
82+
},
83+
{
84+
stderr: /Single executable application is an experimental feature/
85+
});
86+
87+
assert(existsSync(seaPrepBlob));
88+
89+
generateSEA(outputFile, process.execPath, seaPrepBlob);
90+
91+
spawnSyncAndAssert(
92+
outputFile,
93+
{
94+
env: {
95+
NODE_DEBUG_NATIVE: 'SEA,MKSNAPSHOT',
96+
...process.env,
97+
}
98+
},
99+
{
100+
trim: true,
101+
stdout: 'Hello from snapshot with config argv: user-arg',
102+
stderr(output) {
103+
assert.doesNotMatch(
104+
output,
105+
/Single executable application is an experimental feature/);
106+
}
107+
}
108+
);
109+
}

0 commit comments

Comments
 (0)