mirror of
https://github.com/zebrajr/node.git
synced 2026-01-15 12:15:26 +00:00
So that we can use it to handle code caching in a central place. Drive-by: use per-isolate persistent strings for the parameters and mark GetHostDefinedOptions() since it's only used in one compilation unit PR-URL: https://github.com/nodejs/node/pull/52016 Refs: https://github.com/nodejs/node/issues/47472 Reviewed-By: Antoine du Hamel <duhamelantoine1995@gmail.com> Reviewed-By: Geoffrey Booth <webadmin@geoffreybooth.com>
645 lines
21 KiB
C++
645 lines
21 KiB
C++
#include "node_sea.h"
|
|
|
|
#include "blob_serializer_deserializer-inl.h"
|
|
#include "debug_utils-inl.h"
|
|
#include "env-inl.h"
|
|
#include "json_parser.h"
|
|
#include "node_contextify.h"
|
|
#include "node_errors.h"
|
|
#include "node_external_reference.h"
|
|
#include "node_internals.h"
|
|
#include "node_snapshot_builder.h"
|
|
#include "node_union_bytes.h"
|
|
#include "node_v8_platform-inl.h"
|
|
#include "util-inl.h"
|
|
|
|
// The POSTJECT_SENTINEL_FUSE macro is a string of random characters selected by
|
|
// the Node.js project that is present only once in the entire binary. It is
|
|
// used by the postject_has_resource() function to efficiently detect if a
|
|
// resource has been injected. See
|
|
// https://github.com/nodejs/postject/blob/35343439cac8c488f2596d7c4c1dddfec1fddcae/postject-api.h#L42-L45.
|
|
#define POSTJECT_SENTINEL_FUSE "NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2"
|
|
#include "postject-api.h"
|
|
#undef POSTJECT_SENTINEL_FUSE
|
|
|
|
#include <memory>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
using node::ExitCode;
|
|
using v8::ArrayBuffer;
|
|
using v8::BackingStore;
|
|
using v8::Context;
|
|
using v8::Function;
|
|
using v8::FunctionCallbackInfo;
|
|
using v8::HandleScope;
|
|
using v8::Isolate;
|
|
using v8::Local;
|
|
using v8::NewStringType;
|
|
using v8::Object;
|
|
using v8::ScriptCompiler;
|
|
using v8::String;
|
|
using v8::Value;
|
|
|
|
namespace node {
|
|
namespace sea {
|
|
|
|
namespace {
|
|
|
|
SeaFlags operator|(SeaFlags x, SeaFlags y) {
|
|
return static_cast<SeaFlags>(static_cast<uint32_t>(x) |
|
|
static_cast<uint32_t>(y));
|
|
}
|
|
|
|
SeaFlags operator&(SeaFlags x, SeaFlags y) {
|
|
return static_cast<SeaFlags>(static_cast<uint32_t>(x) &
|
|
static_cast<uint32_t>(y));
|
|
}
|
|
|
|
SeaFlags operator|=(/* NOLINT (runtime/references) */ SeaFlags& x, SeaFlags y) {
|
|
return x = x | y;
|
|
}
|
|
|
|
class SeaSerializer : public BlobSerializer<SeaSerializer> {
|
|
public:
|
|
SeaSerializer()
|
|
: BlobSerializer<SeaSerializer>(
|
|
per_process::enabled_debug_list.enabled(DebugCategory::SEA)) {}
|
|
|
|
template <typename T,
|
|
std::enable_if_t<!std::is_same<T, std::string>::value>* = nullptr,
|
|
std::enable_if_t<!std::is_arithmetic<T>::value>* = nullptr>
|
|
size_t Write(const T& data);
|
|
};
|
|
|
|
template <>
|
|
size_t SeaSerializer::Write(const SeaResource& sea) {
|
|
sink.reserve(SeaResource::kHeaderSize + sea.main_code_or_snapshot.size());
|
|
|
|
Debug("Write SEA magic %x\n", kMagic);
|
|
size_t written_total = WriteArithmetic<uint32_t>(kMagic);
|
|
|
|
uint32_t flags = static_cast<uint32_t>(sea.flags);
|
|
Debug("Write SEA flags %x\n", flags);
|
|
written_total += WriteArithmetic<uint32_t>(flags);
|
|
DCHECK_EQ(written_total, SeaResource::kHeaderSize);
|
|
|
|
Debug("Write SEA code path %p, size=%zu\n",
|
|
sea.code_path.data(),
|
|
sea.code_path.size());
|
|
written_total +=
|
|
WriteStringView(sea.code_path, StringLogMode::kAddressAndContent);
|
|
|
|
Debug("Write SEA resource %s %p, size=%zu\n",
|
|
sea.use_snapshot() ? "snapshot" : "code",
|
|
sea.main_code_or_snapshot.data(),
|
|
sea.main_code_or_snapshot.size());
|
|
written_total +=
|
|
WriteStringView(sea.main_code_or_snapshot,
|
|
sea.use_snapshot() ? StringLogMode::kAddressOnly
|
|
: StringLogMode::kAddressAndContent);
|
|
|
|
if (sea.code_cache.has_value()) {
|
|
Debug("Write SEA resource code cache %p, size=%zu\n",
|
|
sea.code_cache->data(),
|
|
sea.code_cache->size());
|
|
written_total +=
|
|
WriteStringView(sea.code_cache.value(), StringLogMode::kAddressOnly);
|
|
}
|
|
|
|
if (!sea.assets.empty()) {
|
|
Debug("Write SEA resource assets size %zu\n", sea.assets.size());
|
|
written_total += WriteArithmetic<size_t>(sea.assets.size());
|
|
for (auto const& [key, content] : sea.assets) {
|
|
Debug("Write SEA resource asset %s at %p, size=%zu\n",
|
|
key,
|
|
content.data(),
|
|
content.size());
|
|
written_total += WriteStringView(key, StringLogMode::kAddressAndContent);
|
|
written_total += WriteStringView(content, StringLogMode::kAddressOnly);
|
|
}
|
|
}
|
|
return written_total;
|
|
}
|
|
|
|
class SeaDeserializer : public BlobDeserializer<SeaDeserializer> {
|
|
public:
|
|
explicit SeaDeserializer(std::string_view v)
|
|
: BlobDeserializer<SeaDeserializer>(
|
|
per_process::enabled_debug_list.enabled(DebugCategory::SEA), v) {}
|
|
|
|
template <typename T,
|
|
std::enable_if_t<!std::is_same<T, std::string>::value>* = nullptr,
|
|
std::enable_if_t<!std::is_arithmetic<T>::value>* = nullptr>
|
|
T Read();
|
|
};
|
|
|
|
template <>
|
|
SeaResource SeaDeserializer::Read() {
|
|
uint32_t magic = ReadArithmetic<uint32_t>();
|
|
Debug("Read SEA magic %x\n", magic);
|
|
|
|
CHECK_EQ(magic, kMagic);
|
|
SeaFlags flags(static_cast<SeaFlags>(ReadArithmetic<uint32_t>()));
|
|
Debug("Read SEA flags %x\n", static_cast<uint32_t>(flags));
|
|
CHECK_EQ(read_total, SeaResource::kHeaderSize);
|
|
|
|
std::string_view code_path =
|
|
ReadStringView(StringLogMode::kAddressAndContent);
|
|
Debug(
|
|
"Read SEA code path %p, size=%zu\n", code_path.data(), code_path.size());
|
|
|
|
bool use_snapshot = static_cast<bool>(flags & SeaFlags::kUseSnapshot);
|
|
std::string_view code =
|
|
ReadStringView(use_snapshot ? StringLogMode::kAddressOnly
|
|
: StringLogMode::kAddressAndContent);
|
|
|
|
Debug("Read SEA resource %s %p, size=%zu\n",
|
|
use_snapshot ? "snapshot" : "code",
|
|
code.data(),
|
|
code.size());
|
|
|
|
std::string_view code_cache;
|
|
if (static_cast<bool>(flags & SeaFlags::kUseCodeCache)) {
|
|
code_cache = ReadStringView(StringLogMode::kAddressOnly);
|
|
Debug("Read SEA resource code cache %p, size=%zu\n",
|
|
code_cache.data(),
|
|
code_cache.size());
|
|
}
|
|
|
|
std::unordered_map<std::string_view, std::string_view> assets;
|
|
if (static_cast<bool>(flags & SeaFlags::kIncludeAssets)) {
|
|
size_t assets_size = ReadArithmetic<size_t>();
|
|
Debug("Read SEA resource assets size %zu\n", assets_size);
|
|
for (size_t i = 0; i < assets_size; ++i) {
|
|
std::string_view key = ReadStringView(StringLogMode::kAddressAndContent);
|
|
std::string_view content = ReadStringView(StringLogMode::kAddressOnly);
|
|
Debug("Read SEA resource asset %s at %p, size=%zu\n",
|
|
key,
|
|
content.data(),
|
|
content.size());
|
|
assets.emplace(key, content);
|
|
}
|
|
}
|
|
return {flags, code_path, code, code_cache, assets};
|
|
}
|
|
|
|
std::string_view FindSingleExecutableBlob() {
|
|
#if !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
|
|
CHECK(IsSingleExecutable());
|
|
static const std::string_view result = []() -> std::string_view {
|
|
size_t size;
|
|
#ifdef __APPLE__
|
|
postject_options options;
|
|
postject_options_init(&options);
|
|
options.macho_segment_name = "NODE_SEA";
|
|
const char* blob = static_cast<const char*>(
|
|
postject_find_resource("NODE_SEA_BLOB", &size, &options));
|
|
#else
|
|
const char* blob = static_cast<const char*>(
|
|
postject_find_resource("NODE_SEA_BLOB", &size, nullptr));
|
|
#endif
|
|
return {blob, size};
|
|
}();
|
|
per_process::Debug(DebugCategory::SEA,
|
|
"Found SEA blob %p, size=%zu\n",
|
|
result.data(),
|
|
result.size());
|
|
return result;
|
|
#else
|
|
UNREACHABLE();
|
|
#endif // !defined(DISABLE_SINGLE_EXECUTABLE_APPLICATION)
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
bool SeaResource::use_snapshot() const {
|
|
return static_cast<bool>(flags & SeaFlags::kUseSnapshot);
|
|
}
|
|
|
|
bool SeaResource::use_code_cache() const {
|
|
return static_cast<bool>(flags & SeaFlags::kUseCodeCache);
|
|
}
|
|
|
|
SeaResource FindSingleExecutableResource() {
|
|
static const SeaResource sea_resource = []() -> SeaResource {
|
|
std::string_view blob = FindSingleExecutableBlob();
|
|
per_process::Debug(DebugCategory::SEA,
|
|
"Found SEA resource %p, size=%zu\n",
|
|
blob.data(),
|
|
blob.size());
|
|
SeaDeserializer deserializer(blob);
|
|
return deserializer.Read<SeaResource>();
|
|
}();
|
|
return sea_resource;
|
|
}
|
|
|
|
bool IsSingleExecutable() {
|
|
return postject_has_resource();
|
|
}
|
|
|
|
void IsSea(const FunctionCallbackInfo<Value>& args) {
|
|
args.GetReturnValue().Set(IsSingleExecutable());
|
|
}
|
|
|
|
void IsExperimentalSeaWarningNeeded(const FunctionCallbackInfo<Value>& args) {
|
|
bool is_building_sea =
|
|
!per_process::cli_options->experimental_sea_config.empty();
|
|
if (is_building_sea) {
|
|
args.GetReturnValue().Set(true);
|
|
return;
|
|
}
|
|
|
|
if (!IsSingleExecutable()) {
|
|
args.GetReturnValue().Set(false);
|
|
return;
|
|
}
|
|
|
|
SeaResource sea_resource = FindSingleExecutableResource();
|
|
args.GetReturnValue().Set(!static_cast<bool>(
|
|
sea_resource.flags & SeaFlags::kDisableExperimentalSeaWarning));
|
|
}
|
|
|
|
void GetCodePath(const FunctionCallbackInfo<Value>& args) {
|
|
DCHECK(IsSingleExecutable());
|
|
|
|
Isolate* isolate = args.GetIsolate();
|
|
|
|
SeaResource sea_resource = FindSingleExecutableResource();
|
|
|
|
Local<String> code_path;
|
|
if (!String::NewFromUtf8(isolate,
|
|
sea_resource.code_path.data(),
|
|
NewStringType::kNormal,
|
|
sea_resource.code_path.length())
|
|
.ToLocal(&code_path)) {
|
|
return;
|
|
}
|
|
|
|
args.GetReturnValue().Set(code_path);
|
|
}
|
|
|
|
std::tuple<int, char**> FixupArgsForSEA(int argc, char** argv) {
|
|
// Repeats argv[0] at position 1 on argv as a replacement for the missing
|
|
// entry point file path.
|
|
if (IsSingleExecutable()) {
|
|
static std::vector<char*> new_argv;
|
|
new_argv.reserve(argc + 2);
|
|
new_argv.emplace_back(argv[0]);
|
|
new_argv.insert(new_argv.end(), argv, argv + argc);
|
|
new_argv.emplace_back(nullptr);
|
|
argc = new_argv.size() - 1;
|
|
argv = new_argv.data();
|
|
}
|
|
|
|
return {argc, argv};
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct SeaConfig {
|
|
std::string main_path;
|
|
std::string output_path;
|
|
SeaFlags flags = SeaFlags::kDefault;
|
|
std::unordered_map<std::string, std::string> assets;
|
|
};
|
|
|
|
std::optional<SeaConfig> ParseSingleExecutableConfig(
|
|
const std::string& config_path) {
|
|
std::string config;
|
|
int r = ReadFileSync(&config, config_path.c_str());
|
|
if (r != 0) {
|
|
const char* err = uv_strerror(r);
|
|
FPrintF(stderr,
|
|
"Cannot read single executable configuration from %s: %s\n",
|
|
config_path,
|
|
err);
|
|
return std::nullopt;
|
|
}
|
|
|
|
SeaConfig result;
|
|
JSONParser parser;
|
|
if (!parser.Parse(config)) {
|
|
FPrintF(stderr, "Cannot parse JSON from %s\n", config_path);
|
|
return std::nullopt;
|
|
}
|
|
|
|
result.main_path =
|
|
parser.GetTopLevelStringField("main").value_or(std::string());
|
|
if (result.main_path.empty()) {
|
|
FPrintF(stderr,
|
|
"\"main\" field of %s is not a non-empty string\n",
|
|
config_path);
|
|
return std::nullopt;
|
|
}
|
|
|
|
result.output_path =
|
|
parser.GetTopLevelStringField("output").value_or(std::string());
|
|
if (result.output_path.empty()) {
|
|
FPrintF(stderr,
|
|
"\"output\" field of %s is not a non-empty string\n",
|
|
config_path);
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::optional<bool> disable_experimental_sea_warning =
|
|
parser.GetTopLevelBoolField("disableExperimentalSEAWarning");
|
|
if (!disable_experimental_sea_warning.has_value()) {
|
|
FPrintF(stderr,
|
|
"\"disableExperimentalSEAWarning\" field of %s is not a Boolean\n",
|
|
config_path);
|
|
return std::nullopt;
|
|
}
|
|
if (disable_experimental_sea_warning.value()) {
|
|
result.flags |= SeaFlags::kDisableExperimentalSeaWarning;
|
|
}
|
|
|
|
std::optional<bool> use_snapshot = parser.GetTopLevelBoolField("useSnapshot");
|
|
if (!use_snapshot.has_value()) {
|
|
FPrintF(
|
|
stderr, "\"useSnapshot\" field of %s is not a Boolean\n", config_path);
|
|
return std::nullopt;
|
|
}
|
|
if (use_snapshot.value()) {
|
|
result.flags |= SeaFlags::kUseSnapshot;
|
|
}
|
|
|
|
std::optional<bool> use_code_cache =
|
|
parser.GetTopLevelBoolField("useCodeCache");
|
|
if (!use_code_cache.has_value()) {
|
|
FPrintF(
|
|
stderr, "\"useCodeCache\" field of %s is not a Boolean\n", config_path);
|
|
return std::nullopt;
|
|
}
|
|
if (use_code_cache.value()) {
|
|
result.flags |= SeaFlags::kUseCodeCache;
|
|
}
|
|
|
|
auto assets_opt = parser.GetTopLevelStringDict("assets");
|
|
if (!assets_opt.has_value()) {
|
|
FPrintF(stderr,
|
|
"\"assets\" field of %s is not a map of strings\n",
|
|
config_path);
|
|
return std::nullopt;
|
|
} else if (!assets_opt.value().empty()) {
|
|
result.flags |= SeaFlags::kIncludeAssets;
|
|
result.assets = std::move(assets_opt.value());
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
ExitCode GenerateSnapshotForSEA(const SeaConfig& config,
|
|
const std::vector<std::string>& args,
|
|
const std::vector<std::string>& exec_args,
|
|
const std::string& builder_script_content,
|
|
const SnapshotConfig& snapshot_config,
|
|
std::vector<char>* snapshot_blob) {
|
|
SnapshotData snapshot;
|
|
// TODO(joyeecheung): make the arguments configurable through the JSON
|
|
// config or a programmatic API.
|
|
std::vector<std::string> patched_args = {args[0], config.main_path};
|
|
ExitCode exit_code = SnapshotBuilder::Generate(&snapshot,
|
|
patched_args,
|
|
exec_args,
|
|
builder_script_content,
|
|
snapshot_config);
|
|
if (exit_code != ExitCode::kNoFailure) {
|
|
return exit_code;
|
|
}
|
|
auto& persistents = snapshot.env_info.principal_realm.persistent_values;
|
|
auto it = std::find_if(
|
|
persistents.begin(), persistents.end(), [](const PropInfo& prop) {
|
|
return prop.name == "snapshot_deserialize_main";
|
|
});
|
|
if (it == persistents.end()) {
|
|
FPrintF(
|
|
stderr,
|
|
"%s does not invoke "
|
|
"v8.startupSnapshot.setDeserializeMainFunction(), which is required "
|
|
"for snapshot scripts used to build single executable applications."
|
|
"\n",
|
|
config.main_path);
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
// We need the temporary variable for copy elision.
|
|
std::vector<char> temp = snapshot.ToBlob();
|
|
*snapshot_blob = std::move(temp);
|
|
return ExitCode::kNoFailure;
|
|
}
|
|
|
|
std::optional<std::string> GenerateCodeCache(std::string_view main_path,
|
|
std::string_view main_script) {
|
|
RAIIIsolate raii_isolate(SnapshotBuilder::GetEmbeddedSnapshotData());
|
|
Isolate* isolate = raii_isolate.get();
|
|
|
|
v8::Isolate::Scope isolate_scope(isolate);
|
|
HandleScope handle_scope(isolate);
|
|
|
|
Local<Context> context = Context::New(isolate);
|
|
Context::Scope context_scope(context);
|
|
|
|
errors::PrinterTryCatch bootstrapCatch(
|
|
isolate, errors::PrinterTryCatch::kPrintSourceLine);
|
|
|
|
Local<String> filename;
|
|
if (!String::NewFromUtf8(isolate,
|
|
main_path.data(),
|
|
NewStringType::kNormal,
|
|
main_path.length())
|
|
.ToLocal(&filename)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
Local<String> content;
|
|
if (!String::NewFromUtf8(isolate,
|
|
main_script.data(),
|
|
NewStringType::kNormal,
|
|
main_script.length())
|
|
.ToLocal(&content)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::vector<Local<String>> parameters = {
|
|
FIXED_ONE_BYTE_STRING(isolate, "exports"),
|
|
FIXED_ONE_BYTE_STRING(isolate, "require"),
|
|
FIXED_ONE_BYTE_STRING(isolate, "module"),
|
|
FIXED_ONE_BYTE_STRING(isolate, "__filename"),
|
|
FIXED_ONE_BYTE_STRING(isolate, "__dirname"),
|
|
};
|
|
|
|
// TODO(RaisinTen): Using the V8 code cache prevents us from using `import()`
|
|
// in the SEA code. Support it.
|
|
// Refs: https://github.com/nodejs/node/pull/48191#discussion_r1213271430
|
|
Local<Function> fn;
|
|
if (!contextify::CompileFunction(context, filename, content, ¶meters)
|
|
.ToLocal(&fn)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
std::unique_ptr<ScriptCompiler::CachedData> cache{
|
|
ScriptCompiler::CreateCodeCacheForFunction(fn)};
|
|
std::string code_cache(cache->data, cache->data + cache->length);
|
|
return code_cache;
|
|
}
|
|
|
|
int BuildAssets(const std::unordered_map<std::string, std::string>& config,
|
|
std::unordered_map<std::string, std::string>* assets) {
|
|
for (auto const& [key, path] : config) {
|
|
std::string blob;
|
|
int r = ReadFileSync(&blob, path.c_str());
|
|
if (r != 0) {
|
|
const char* err = uv_strerror(r);
|
|
FPrintF(stderr, "Cannot read asset %s: %s\n", path.c_str(), err);
|
|
return r;
|
|
}
|
|
assets->emplace(key, std::move(blob));
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
ExitCode GenerateSingleExecutableBlob(
|
|
const SeaConfig& config,
|
|
const std::vector<std::string>& args,
|
|
const std::vector<std::string>& exec_args) {
|
|
std::string main_script;
|
|
// TODO(joyeecheung): unify the file utils.
|
|
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, err);
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
|
|
std::vector<char> snapshot_blob;
|
|
bool builds_snapshot_from_main =
|
|
static_cast<bool>(config.flags & SeaFlags::kUseSnapshot);
|
|
if (builds_snapshot_from_main) {
|
|
// TODO(joyeecheung): allow passing snapshot configuration in SEA configs.
|
|
SnapshotConfig snapshot_config;
|
|
snapshot_config.builder_script_path = main_script;
|
|
ExitCode exit_code = GenerateSnapshotForSEA(
|
|
config, args, exec_args, main_script, snapshot_config, &snapshot_blob);
|
|
if (exit_code != ExitCode::kNoFailure) {
|
|
return exit_code;
|
|
}
|
|
}
|
|
|
|
std::optional<std::string_view> optional_sv_code_cache;
|
|
std::string code_cache;
|
|
if (static_cast<bool>(config.flags & SeaFlags::kUseCodeCache)) {
|
|
if (builds_snapshot_from_main) {
|
|
FPrintF(stderr,
|
|
"\"useCodeCache\" is redundant when \"useSnapshot\" is true\n");
|
|
} else {
|
|
std::optional<std::string> optional_code_cache =
|
|
GenerateCodeCache(config.main_path, main_script);
|
|
if (!optional_code_cache.has_value()) {
|
|
FPrintF(stderr, "Cannot generate V8 code cache\n");
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
code_cache = optional_code_cache.value();
|
|
optional_sv_code_cache = code_cache;
|
|
}
|
|
}
|
|
|
|
std::unordered_map<std::string, std::string> assets;
|
|
if (!config.assets.empty() && BuildAssets(config.assets, &assets) != 0) {
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
std::unordered_map<std::string_view, std::string_view> assets_view;
|
|
for (auto const& [key, content] : assets) {
|
|
assets_view.emplace(key, content);
|
|
}
|
|
SeaResource sea{
|
|
config.flags,
|
|
config.main_path,
|
|
builds_snapshot_from_main
|
|
? std::string_view{snapshot_blob.data(), snapshot_blob.size()}
|
|
: std::string_view{main_script.data(), main_script.size()},
|
|
optional_sv_code_cache,
|
|
assets_view};
|
|
|
|
SeaSerializer serializer;
|
|
serializer.Write(sea);
|
|
|
|
uv_buf_t buf = uv_buf_init(serializer.sink.data(), serializer.sink.size());
|
|
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, err);
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
|
|
FPrintF(stderr,
|
|
"Wrote single executable preparation blob to %s\n",
|
|
config.output_path);
|
|
return ExitCode::kNoFailure;
|
|
}
|
|
|
|
} // anonymous namespace
|
|
|
|
ExitCode BuildSingleExecutableBlob(const std::string& config_path,
|
|
const std::vector<std::string>& args,
|
|
const std::vector<std::string>& exec_args) {
|
|
std::optional<SeaConfig> config_opt =
|
|
ParseSingleExecutableConfig(config_path);
|
|
if (config_opt.has_value()) {
|
|
ExitCode code =
|
|
GenerateSingleExecutableBlob(config_opt.value(), args, exec_args);
|
|
return code;
|
|
}
|
|
|
|
return ExitCode::kGenericUserError;
|
|
}
|
|
|
|
void GetAsset(const FunctionCallbackInfo<Value>& args) {
|
|
CHECK_EQ(args.Length(), 1);
|
|
CHECK(args[0]->IsString());
|
|
Utf8Value key(args.GetIsolate(), args[0]);
|
|
SeaResource sea_resource = FindSingleExecutableResource();
|
|
if (sea_resource.assets.empty()) {
|
|
return;
|
|
}
|
|
auto it = sea_resource.assets.find(*key);
|
|
if (it == sea_resource.assets.end()) {
|
|
return;
|
|
}
|
|
// We cast away the constness here, the JS land should ensure that
|
|
// the data is not mutated.
|
|
std::unique_ptr<v8::BackingStore> store = ArrayBuffer::NewBackingStore(
|
|
const_cast<char*>(it->second.data()),
|
|
it->second.size(),
|
|
[](void*, size_t, void*) {},
|
|
nullptr);
|
|
Local<ArrayBuffer> ab = ArrayBuffer::New(args.GetIsolate(), std::move(store));
|
|
args.GetReturnValue().Set(ab);
|
|
}
|
|
|
|
void Initialize(Local<Object> target,
|
|
Local<Value> unused,
|
|
Local<Context> context,
|
|
void* priv) {
|
|
SetMethod(context, target, "isSea", IsSea);
|
|
SetMethod(context,
|
|
target,
|
|
"isExperimentalSeaWarningNeeded",
|
|
IsExperimentalSeaWarningNeeded);
|
|
SetMethod(context, target, "getCodePath", GetCodePath);
|
|
SetMethod(context, target, "getAsset", GetAsset);
|
|
}
|
|
|
|
void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
|
registry->Register(IsSea);
|
|
registry->Register(IsExperimentalSeaWarningNeeded);
|
|
registry->Register(GetCodePath);
|
|
registry->Register(GetAsset);
|
|
}
|
|
|
|
} // namespace sea
|
|
} // namespace node
|
|
|
|
NODE_BINDING_CONTEXT_AWARE_INTERNAL(sea, node::sea::Initialize)
|
|
NODE_BINDING_EXTERNAL_REFERENCE(sea, node::sea::RegisterExternalReferences)
|