From 47fe41aa0750f877d8e569e596acac6e2978f373 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Mon, 13 Mar 2023 22:45:42 +0100 Subject: [PATCH 1/5] sea: use JSON configuration and blob content for SEA --- doc/api/cli.md | 12 ++ doc/api/single-executable-applications.md | 91 +++++--- ...g-single-executable-application-support.md | 2 +- node.gyp | 2 + src/json_parser.cc | 80 +++++++ src/json_parser.h | 39 ++++ src/node.cc | 5 + src/node_errors.cc | 28 ++- src/node_errors.h | 16 ++ src/node_options.cc | 5 + src/node_options.h | 1 + src/node_sea.cc | 92 +++++++- src/node_sea.h | 3 +- .../test-single-executable-application.js | 28 ++- ...st-single-executable-blob-config-errors.js | 204 ++++++++++++++++++ .../test-single-executable-blob-config.js | 50 +++++ 16 files changed, 613 insertions(+), 45 deletions(-) create mode 100644 src/json_parser.cc create mode 100644 src/json_parser.h create mode 100644 test/parallel/test-single-executable-blob-config-errors.js create mode 100644 test/parallel/test-single-executable-blob-config.js diff --git a/doc/api/cli.md b/doc/api/cli.md index cda8232ac91d9f..0b0e643e5d71d7 100644 --- a/doc/api/cli.md +++ b/doc/api/cli.md @@ -588,6 +588,16 @@ added: v16.6.0 Use this flag to disable top-level await in REPL. +### `--experimental-sea-config` + + + +Use this flag to generate a blob that can be injected in to the Node.js +binary to produce a [single executable application][]. See the documentation +about [this configuration][`--experimental-sea-config`] for details. + ### `--experimental-shadow-realm` -Use this flag to generate a blob that can be injected in to the Node.js +> Stability: 1 - Experimental + +Use this flag to generate a blob that can be injected into the Node.js binary to produce a [single executable application][]. See the documentation about [this configuration][`--experimental-sea-config`] for details. diff --git a/doc/api/single-executable-applications.md b/doc/api/single-executable-applications.md index c0655d00e5f1dc..e1d227a02e0d28 100644 --- a/doc/api/single-executable-applications.md +++ b/doc/api/single-executable-applications.md @@ -70,7 +70,7 @@ tool, [postject][]: * `hello` - The name of the copy of the `node` executable created in step 2. * `NODE_SEA_BLOB` - The name of the resource / note / section in the binary where the contents of the blob will be stored. - * `hello.js` - The name of the blob created in step 1. + * `sea-prep.blob` - The name of the blob created in step 1. * `--sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2` - The [fuse][] used by the Node.js project to detect if a file has been injected. * `--macho-segment-name NODE_SEA` (only needed on macOS) - The name of the @@ -81,13 +81,13 @@ tool, [postject][]: * On systems other than macOS: ```console - $ npx postject hello NODE_SEA_BLOB hello.js \ + $ npx postject hello NODE_SEA_BLOB sea-prep.blob \ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 ``` * On macOS: ```console - $ npx postject hello NODE_SEA_BLOB hello.js \ + $ npx postject hello NODE_SEA_BLOB sea-prep.blob \ --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2 \ --macho-segment-name NODE_SEA ``` From d8d7f13db9195ff1ed96c428362ea71b13c8919e Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Wed, 5 Apr 2023 18:41:28 +0200 Subject: [PATCH 4/5] fixup! fixup! sea: use JSON configuration and blob content for SEA --- src/node_sea.cc | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/node_sea.cc b/src/node_sea.cc index dfe0a29df5785c..e089c688acc9e1 100644 --- a/src/node_sea.cc +++ b/src/node_sea.cc @@ -1,5 +1,6 @@ #include "node_sea.h" +#include "debug_utils-inl.h" #include "env-inl.h" #include "json_parser.h" #include "node_external_reference.h" @@ -32,6 +33,10 @@ using v8::Value; namespace node { namespace sea { +// A special number that will appear at the beginning of the single executable +// preparation blobs ready to be injected into the binary. We use this to check +// that the data given to us are intended for building single executable +// applications. static const uint32_t kMagic = 0x143da20; std::string_view FindSingleExecutableCode() { @@ -91,9 +96,9 @@ std::optional ParseSingleExecutableConfig( int r = ReadFileSync(&config, config_path.c_str()); if (r != 0) { const char* err = uv_strerror(r); - fprintf(stderr, + FPrintF(stderr, "Cannot read single executable configuration from %s: %s\n", - config_path.c_str(), + config_path, err); return std::nullopt; } @@ -101,23 +106,24 @@ std::optional ParseSingleExecutableConfig( SeaConfig result; JSONParser parser; if (!parser.Parse(config)) { - fprintf(stderr, "Cannot parse JSON from %s\n", config_path.c_str()); + FPrintF(stderr, "Cannot parse JSON from %s\n", config_path); return std::nullopt; } result.main_path = parser.GetTopLevelField("main").value_or(std::string()); if (result.main_path.empty()) { - fprintf( - stderr, "\"main\" field of %s is not a string\n", config_path.c_str()); + FPrintF(stderr, + "\"main\" field of %s is not a non-empty string\n", + config_path); return std::nullopt; } result.output_path = parser.GetTopLevelField("output").value_or(std::string()); if (result.output_path.empty()) { - fprintf(stderr, - "\"output\" field of %s is not a string\n", - config_path.c_str()); + FPrintF(stderr, + "\"output\" field of %s is not a non-empty string\n", + config_path); return std::nullopt; } @@ -130,10 +136,7 @@ bool GenerateSingleExecutableBlob(const SeaConfig& config) { int r = ReadFileSync(&main_script, config.main_path.c_str()); if (r != 0) { const char* err = uv_strerror(r); - fprintf(stderr, - "Cannot read main script %s:%s\n", - config.main_path.c_str(), - err); + FPrintF(stderr, "Cannot read main script %s:%s\n", config.main_path, err); return false; } @@ -149,16 +152,13 @@ bool GenerateSingleExecutableBlob(const SeaConfig& config) { r = WriteFileSync(config.output_path.c_str(), buf); if (r != 0) { const char* err = uv_strerror(r); - fprintf(stderr, - "Cannot write output to %s:%s\n", - config.output_path.c_str(), - err); + FPrintF(stderr, "Cannot write output to %s:%s\n", config.output_path, err); return false; } - fprintf(stderr, + FPrintF(stderr, "Wrote single executable preparation blob to %s\n", - config.output_path.c_str()); + config.output_path); return true; } From 58ec5e942c1185b94a2c12fb10806e7d8ca58025 Mon Sep 17 00:00:00 2001 From: Joyee Cheung Date: Thu, 6 Apr 2023 15:46:32 +0200 Subject: [PATCH 5/5] fixup! fixup! fixup! sea: use JSON configuration and blob content for SEA --- src/node_sea.cc | 4 ++++ test/parallel/test-single-executable-blob-config-errors.js | 6 +++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/src/node_sea.cc b/src/node_sea.cc index e089c688acc9e1..5936dc7de9a0d2 100644 --- a/src/node_sea.cc +++ b/src/node_sea.cc @@ -85,6 +85,8 @@ std::tuple FixupArgsForSEA(int argc, char** argv) { return {argc, argv}; } +namespace { + struct SeaConfig { std::string main_path; std::string output_path; @@ -162,6 +164,8 @@ bool GenerateSingleExecutableBlob(const SeaConfig& config) { return true; } +} // anonymous namespace + ExitCode BuildSingleExecutableBlob(const std::string& config_path) { std::optional config_opt = ParseSingleExecutableConfig(config_path); diff --git a/test/parallel/test-single-executable-blob-config-errors.js b/test/parallel/test-single-executable-blob-config-errors.js index 832383540187a6..9c4013f7dc6f66 100644 --- a/test/parallel/test-single-executable-blob-config-errors.js +++ b/test/parallel/test-single-executable-blob-config-errors.js @@ -74,7 +74,7 @@ const { join } = require('path'); assert.strictEqual(child.status, 1); assert( stderr.includes( - `"main" field of ${config} is not a string` + `"main" field of ${config} is not a non-empty string` ) ); } @@ -92,7 +92,7 @@ const { join } = require('path'); assert.strictEqual(child.status, 1); assert( stderr.includes( - `"main" field of ${config} is not a string` + `"main" field of ${config} is not a non-empty string` ) ); } @@ -110,7 +110,7 @@ const { join } = require('path'); assert.strictEqual(child.status, 1); assert( stderr.includes( - `"output" field of ${config} is not a string` + `"output" field of ${config} is not a non-empty string` ) ); }