blob: a981fafd76f1d802784dc0d767e0698ce173595d [file] [log] [blame]
// Copyright 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <fcntl.h>
#include <iostream>
#include <set>
#include <string>
#include "dart/runtime/include/dart_api.h"
#include "dart/runtime/include/dart_native_api.h"
#include "dart/runtime/include/dart_tools_api.h"
#include "lib/ftl/arraysize.h"
#include "lib/ftl/command_line.h"
#include "lib/ftl/files/directory.h"
#include "lib/ftl/files/eintr_wrapper.h"
#include "lib/ftl/files/file_descriptor.h"
#include "lib/ftl/files/file.h"
#include "lib/ftl/files/symlink.h"
#include "lib/ftl/files/unique_fd.h"
#include "lib/ftl/logging.h"
#include "lib/tonic/converter/dart_converter.h"
#include "lib/tonic/file_loader/file_loader.h"
extern "C" {
extern const uint8_t kDartVmSnapshotData[];
extern const uint8_t kDartVmSnapshotInstructions[];
extern const uint8_t kDartIsolateSnapshotData[];
extern const uint8_t kDartIsolateSnapshotInstructions[];
}
namespace {
using tonic::ToDart;
constexpr char kHelp[] = "help";
constexpr char kPackages[] = "packages";
constexpr char kSnapshot[] = "snapshot";
constexpr char kDepfile[] = "depfile";
constexpr char kBuildOutput[] = "build-output";
constexpr char kPrintDeps[] = "print-deps";
constexpr char kCompileAll[] = "compile-all";
const char* kDartArgs[] = {
// clang-format off
"--enable_mirrors=false",
"--load_deferred_eagerly=true",
"--conditional_directives",
// TODO(chinmaygarde): The experimental interpreter for iOS device targets
// does not support all these flags. The build process uses its own version
// of this snapshotter. Till support for all these flags is added, make
// sure the snapshotter does not error out on unrecognized flags.
"--ignore-unrecognized-flags",
// clang-format on
};
void Usage() {
std::cerr
<< "Usage: sky_snapshot --" << kPackages << "=PACKAGES" << std::endl
<< " [ --" << kSnapshot << "=OUTPUT_SNAPSHOT ]"
<< std::endl
<< " [ --" << kDepfile << "=DEPFILE ]" << std::endl
<< " [ --" << kBuildOutput << "=BUILD_OUTPUT ]"
<< std::endl
<< std::endl
<< " MAIN_DART" << std::endl
<< " * PACKAGES is the '.packages' file that defines where to find Dart "
"packages."
<< std::endl
<< " * OUTPUT_SNAPSHOT is the file to write the snapshot into."
<< std::endl
<< " * DEPFILE is the file into which to write the '.d' depedendency "
"information into."
<< std::endl
<< " * BUILD_OUTPUT determines the target name used in the " << std::endl
<< " DEPFILE. (Required if DEPFILE is provided.) " << std::endl;
}
class DartScope {
public:
DartScope(Dart_Isolate isolate) {
Dart_EnterIsolate(isolate);
Dart_EnterScope();
}
~DartScope() {
Dart_ExitScope();
Dart_ExitIsolate();
}
};
void InitDartVM() {
FTL_CHECK(Dart_SetVMFlags(arraysize(kDartArgs), kDartArgs));
Dart_InitializeParams params = {};
params.version = DART_INITIALIZE_PARAMS_CURRENT_VERSION;
params.vm_snapshot_data = kDartVmSnapshotData;
params.vm_snapshot_instructions = kDartVmSnapshotInstructions;
char* error = Dart_Initialize(&params);
if (error)
FTL_LOG(FATAL) << error;
}
Dart_Isolate CreateDartIsolate() {
char* error = nullptr;
Dart_Isolate isolate =
Dart_CreateIsolate("dart:snapshot", "main", kDartIsolateSnapshotData,
kDartIsolateSnapshotInstructions,
nullptr, nullptr, &error);
FTL_CHECK(isolate) << error;
Dart_ExitIsolate();
return isolate;
}
tonic::FileLoader* g_loader = nullptr;
tonic::FileLoader& GetLoader() {
if (!g_loader)
g_loader = new tonic::FileLoader();
return *g_loader;
}
Dart_Handle HandleLibraryTag(Dart_LibraryTag tag,
Dart_Handle library,
Dart_Handle url) {
return GetLoader().HandleLibraryTag(tag, library, url);
}
static const char StubNativeFunctionName[] = "StubNativeFunction";
void StubNativeFunction(Dart_NativeArguments arguments) {
// This is a stub function for the resolver
Dart_SetReturnValue(
arguments, Dart_NewApiError("<EMBEDDER DID NOT SETUP NATIVE RESOLVER>"));
}
static Dart_NativeFunction StubNativeLookup(Dart_Handle name,
int argument_count,
bool* auto_setup_scope) {
return &StubNativeFunction;
}
static const uint8_t* StubNativeSymbol(Dart_NativeFunction nf) {
return reinterpret_cast<const uint8_t*>(StubNativeFunctionName);
}
// Registers a dummy native symbol resolver on all loaded libraries that do not
// already have a native symbol resolver. This is necessary because
// `--compile-all` will try and resolve all native functions. The function
// returned by the dummy native symbol resolver will never be invoked.
static void SetupStubNativeResolvers() {
Dart_Handle libraries = Dart_GetLoadedLibraries();
DART_CHECK_VALID(libraries);
intptr_t libraries_length;
DART_CHECK_VALID(Dart_ListLength(libraries, &libraries_length));
for (intptr_t i = 0; i < libraries_length; i++) {
Dart_Handle library = Dart_ListGetAt(libraries, i);
DART_CHECK_VALID(library);
Dart_NativeEntryResolver old_resolver = NULL;
DART_CHECK_VALID(Dart_GetNativeResolver(library, &old_resolver));
if (old_resolver == NULL) {
Dart_Handle result =
Dart_SetNativeResolver(library, &StubNativeLookup, &StubNativeSymbol);
DART_CHECK_VALID(result);
}
}
}
std::vector<char> CreateSnapshot() {
uint8_t* buffer = nullptr;
intptr_t size = 0;
DART_CHECK_VALID(Dart_CreateScriptSnapshot(&buffer, &size));
const char* begin = reinterpret_cast<const char*>(buffer);
return std::vector<char>(begin, begin + size);
}
bool WriteDepfile(const std::string& path,
const std::string& build_output,
const std::set<std::string>& deps) {
std::string current_directory = files::GetCurrentDirectory();
std::string output = build_output + ":";
for (const auto& dep : deps) {
std::string file = dep;
FTL_DCHECK(!file.empty());
if (file[0] != '/')
file = current_directory + "/" + file;
std::string resolved_file;
if (files::ReadSymbolicLink(file, &resolved_file)) {
output += " " + resolved_file;
} else {
output += " " + file;
}
}
return files::WriteFile(path, output.data(), output.size());
}
int CreateSnapshot(const ftl::CommandLine& command_line) {
if (command_line.HasOption(kHelp, nullptr)) {
Usage();
return 0;
}
if (command_line.positional_args().empty()) {
Usage();
return 1;
}
std::string packages;
if (!command_line.GetOptionValue(kPackages, &packages)) {
std::cerr << "error: Need --" << kPackages << std::endl;
return 1;
}
std::vector<std::string> args = command_line.positional_args();
if (args.size() != 1) {
std::cerr << "error: Need one position argument. Got " << args.size() << "."
<< std::endl;
return 1;
}
std::string main_dart = args[0];
const bool print_deps_mode = command_line.HasOption(kPrintDeps, nullptr);
std::string snapshot;
std::string depfile;
std::string build_output;
if (!print_deps_mode) {
if (!command_line.GetOptionValue(kSnapshot, &snapshot)) {
std::cerr << "error: Need --" << kSnapshot << "." << std::endl;
return 1;
}
if (command_line.GetOptionValue(kDepfile, &depfile) &&
!command_line.GetOptionValue(kBuildOutput, &build_output)) {
std::cerr << "error: Need --" << kBuildOutput << " if --" << kDepfile
<< " is specified." << std::endl;
return 1;
}
}
InitDartVM();
tonic::FileLoader& loader = GetLoader();
if (!loader.LoadPackagesMap(packages))
return 1;
Dart_Isolate isolate = CreateDartIsolate();
FTL_CHECK(isolate) << "Failed to create isolate.";
DartScope scope(isolate);
DART_CHECK_VALID(Dart_SetLibraryTagHandler(HandleLibraryTag));
Dart_Handle load_result =
Dart_LoadScript(ToDart(main_dart), Dart_Null(),
ToDart(loader.Fetch(main_dart)), 0, 0);
if (Dart_IsError(load_result)) {
std::cerr << "error: Failed to load main script:"
<< std::endl
<< Dart_GetError(load_result)
<< std::endl;
return 1;
}
SetupStubNativeResolvers();
if (print_deps_mode) {
if (Dart_IsError(load_result)) {
// Loading / parsing the source resulted in an error. Report the error
// to stderr and exit.
std::cerr << Dart_GetError(load_result) << std::endl;
return 1;
}
// The script has been loaded, print out the minimal dependencies to run.
for (const auto& dep : loader.url_dependencies()) {
std::string file = dep;
FTL_DCHECK(!file.empty());
std::cout << file << "\n";
}
} else {
DART_CHECK_VALID(load_result);
DART_CHECK_VALID(Dart_FinalizeLoading(false));
// The script has been loaded, generate a snapshot.
std::vector<char> snapshot_blob = CreateSnapshot();
if (!snapshot.empty() &&
!files::WriteFile(snapshot, snapshot_blob.data(), snapshot_blob.size())) {
std::cerr << "error: Failed to write snapshot to '" << snapshot << "'."
<< std::endl;
return 1;
}
if (!depfile.empty() &&
!WriteDepfile(depfile, build_output, loader.dependencies())) {
std::cerr << "error: Failed to write depfile to '" << depfile << "'."
<< std::endl;
return 1;
}
// Run compile-all *after* generating the snapshot to avoid adding
// unnecessary compilation related artifacts to the snapshot.
const bool compile_all_mode = command_line.HasOption(kCompileAll, nullptr);
if (compile_all_mode) {
// Compile all the code loaded into the isolate. This will eagerly detect
// syntax errors.
Dart_Handle compile_all_results = Dart_CompileAll();
if (Dart_IsError(compile_all_results)) {
std::cerr << "error: Compilation errors detected:"
<< std::endl
<< Dart_GetError(compile_all_results)
<< std::endl;
return 1;
}
}
}
return 0;
}
} // namespace
int main(int argc, const char* argv[]) {
return CreateSnapshot(ftl::CommandLineFromArgcArgv(argc, argv));
}