[ CLI ] Add support for passing VM options to Dart runtimes via DART_VM_OPTIONS

The DART_VM_OPTIONS environment variable allows for users to specify a
set of VM options to be processed by the Dart runtime in a
self-contained executable created by `dart compile exe`.

DART_VM_OPTIONS should be a comma separated list of options and flags
with no whitespace. Options that accept multiple values as a list of
comma separated values are not supported and will result in argument
parsing failing.

Fixes https://github.com/dart-lang/sdk/issues/54281

TEST=compile_test.dart

Change-Id: I1d94ab1b992753a7dd69da722c051c9464d6d1cf
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/353820
Reviewed-by: Siva Annamalai <asiva@google.com>
Commit-Queue: Ben Konyi <bkonyi@google.com>
This commit is contained in:
Ben Konyi 2024-02-26 21:07:40 +00:00 committed by Commit Queue
parent f6e1f6306d
commit ce642d040f
5 changed files with 183 additions and 30 deletions

View file

@ -81,6 +81,20 @@
[#53218]: https://github.com/dart-lang/sdk/issues/53218
[#53785]: https://github.com/dart-lang/sdk/issues/53785
### Dart Runtime
- Dart VM flags and options can now be provided to any executable
generated using `dart compile exe` via the `DART_VM_OPTIONS` environment
variable. `DART_VM_OPTIONS` should be set to a list of comma-separated flags
and options with no whitespace. Options that allow for multiple values to be
provided as comma-separated values are not supported
(e.g., `--timeline-streams=Dart,GC,Compiler`).
Example of a valid `DART_VM_OPTIONS` environment variable:
```bash
DART_VM_OPTIONS=--random_seed=42,--verbose_gc
```
## 3.3.0
### Language

View file

@ -738,6 +738,42 @@ void main() {}
expect(result.stdout, contains('sound'));
}, skip: isRunningOnIA32);
test('Compile and run exe with DART_VM_OPTIONS', () async {
final p = project(mainSrc: '''void main() {
// Empty
}''');
final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));
final outFile = path.canonicalize(path.join(p.dirPath, 'myexe'));
var result = await p.run(
[
'compile',
'exe',
'-o',
outFile,
inFile,
],
);
expect(result.stdout, isNot(contains(soundNullSafetyMessage)));
expect(result.stderr, isEmpty);
expect(result.exitCode, 0);
expect(File(outFile).existsSync(), true,
reason: 'File not found: $outFile');
result = Process.runSync(
outFile,
[],
environment: <String, String>{
'DART_VM_OPTIONS': '--help,--verbose',
},
);
expect(result.stderr, isEmpty);
expect(result.stdout, contains('vm_name'));
expect(result.exitCode, 255);
}, skip: isRunningOnIA32);
test('Compile exe without info', () async {
final p = project(mainSrc: '''void main() {}''');
final inFile = path.canonicalize(path.join(p.dirPath, p.relativeFilePath));

View file

@ -1192,6 +1192,39 @@ void main(int argc, char** argv) {
}
vm_options.AddArgument("--new_gen_growth_factor=4");
auto parse_arguments = [&](int argc, char** argv,
CommandLineOptions* vm_options,
CommandLineOptions* dart_options) {
bool success = Options::ParseArguments(
argc, argv, vm_run_app_snapshot, vm_options, &script_name, dart_options,
&print_flags_seen, &verbose_debug_seen);
if (!success) {
if (Options::help_option()) {
Options::PrintUsage();
Platform::Exit(0);
} else if (Options::version_option()) {
Options::PrintVersion();
Platform::Exit(0);
} else if (print_flags_seen) {
// Will set the VM flags, print them out and then we exit as no
// script was specified on the command line.
char* error =
Dart_SetVMFlags(vm_options->count(), vm_options->arguments());
if (error != nullptr) {
Syslog::PrintErr("Setting VM flags failed: %s\n", error);
free(error);
Platform::Exit(kErrorExitCode);
}
Platform::Exit(0);
} else {
// This usage error case will only be invoked when
// Options::disable_dart_dev() is false.
Options::PrintUsage();
Platform::Exit(kErrorExitCode);
}
}
};
AppSnapshot* app_snapshot = nullptr;
#if defined(DART_PRECOMPILED_RUNTIME)
// If the executable binary contains the runtime together with an appended
@ -1213,40 +1246,23 @@ void main(int argc, char** argv) {
for (int i = 1; i < argc; i++) {
dart_options.AddArgument(argv[i]);
}
// Parse DART_VM_OPTIONS options.
int env_argc = 0;
char** env_argv = Options::GetEnvArguments(&env_argc);
if (env_argv != nullptr) {
// Any Dart options that are generated based on parsing DART_VM_OPTIONS
// are useless, so we'll throw them away rather than passing them along.
CommandLineOptions tmp_options(env_argc + EXTRA_VM_ARGUMENTS);
parse_arguments(env_argc, env_argv, &vm_options, &tmp_options);
}
}
}
#endif
// Parse command line arguments.
if (app_snapshot == nullptr) {
bool success = Options::ParseArguments(
argc, argv, vm_run_app_snapshot, &vm_options, &script_name,
&dart_options, &print_flags_seen, &verbose_debug_seen);
if (!success) {
if (Options::help_option()) {
Options::PrintUsage();
Platform::Exit(0);
} else if (Options::version_option()) {
Options::PrintVersion();
Platform::Exit(0);
} else if (print_flags_seen) {
// Will set the VM flags, print them out and then we exit as no
// script was specified on the command line.
char* error =
Dart_SetVMFlags(vm_options.count(), vm_options.arguments());
if (error != nullptr) {
Syslog::PrintErr("Setting VM flags failed: %s\n", error);
free(error);
Platform::Exit(kErrorExitCode);
}
Platform::Exit(0);
} else {
// This usage error case will only be invoked when
// Options::disable_dart_dev() is false.
Options::PrintUsage();
Platform::Exit(kErrorExitCode);
}
}
parse_arguments(argc, argv, &vm_options, &dart_options);
}
DartUtils::SetEnvironment(Options::environment());
@ -1452,7 +1468,7 @@ void main(int argc, char** argv) {
}
// Free environment if any.
Options::DestroyEnvironment();
Options::Cleanup();
Platform::Exit(global_exit_code);
}

View file

@ -261,6 +261,13 @@ bool Options::ProcessEnvironmentOption(const char* arg,
&Options::environment_);
}
void Options::Cleanup() {
#if defined(DART_PRECOMPILED_RUNTIME)
DestroyEnvArgv();
#endif
DestroyEnvironment();
}
void Options::DestroyEnvironment() {
if (environment_ != nullptr) {
for (SimpleHashMap::Entry* p = environment_->Start(); p != nullptr;
@ -273,6 +280,71 @@ void Options::DestroyEnvironment() {
}
}
#if defined(DART_PRECOMPILED_RUNTIME)
// Retrieves the set of arguments stored in the DART_VM_OPTIONS environment
// variable.
//
// DART_VM_OPTIONS should contain a list of comma-separated options and flags
// with no spaces. Options that support providing multiple values as
// comma-separated lists (e.g., --timeline-streams=Dart,GC,Compiler) are not
// supported and will cause argument parsing to fail.
char** Options::GetEnvArguments(int* argc) {
ASSERT(argc != nullptr);
const char* env_args_str = std::getenv("DART_VM_OPTIONS");
if (env_args_str == nullptr) {
*argc = 0;
return nullptr;
}
intptr_t n = strlen(env_args_str);
if (n == 0) {
return nullptr;
}
// Find the number of arguments based on the number of ','s.
//
// WARNING: this won't work for arguments that support CSVs. There's less
// than a handful of options that support multiple values. If we want to
// support this case, we need to determine a way to specify groupings of CSVs
// in environment variables.
int arg_count = 1;
for (int i = 0; i < n; ++i) {
// Ignore the last comma if it's the last character in the string.
if (env_args_str[i] == ',' && i + 1 != n) {
arg_count++;
}
}
env_argv_ = new char*[arg_count];
env_argc_ = arg_count;
*argc = arg_count;
int current_arg = 0;
char* token;
char* rest = const_cast<char*>(env_args_str);
// Split out the individual arguments.
while ((token = strtok_r(rest, ",", &rest)) != nullptr) {
// TODO(bkonyi): consider stripping leading/trailing whitespace from
// arguments.
env_argv_[current_arg++] = Utils::StrNDup(token, rest - token);
}
return env_argv_;
}
char** Options::env_argv_ = nullptr;
int Options::env_argc_ = 0;
void Options::DestroyEnvArgv() {
for (int i = 0; i < env_argc_; ++i) {
free(env_argv_[i]);
}
delete[] env_argv_;
env_argv_ = nullptr;
}
#endif // defined(DART_PRECOMPILED_RUNTIME)
bool Options::ExtractPortAndAddress(const char* option_value,
int* out_port,
const char** out_ip,

View file

@ -157,9 +157,19 @@ class Options {
static void PrintUsage();
static void PrintVersion();
static void DestroyEnvironment();
static void Cleanup();
#if defined(DART_PRECOMPILED_RUNTIME)
// Get the list of options in DART_VM_OPTIONS.
static char** GetEnvArguments(int* argc);
#endif // defined(DART_PRECOMPILED_RUNTIME)
private:
static void DestroyEnvironment();
#if defined(DART_PRECOMPILED_RUNTIME)
static void DestroyEnvArgv();
#endif // defined(DART_PRECOMPILED_RUNTIME)
#define STRING_OPTION_DECL(flag, variable) static const char* variable##_;
STRING_OPTIONS_LIST(STRING_OPTION_DECL)
#undef STRING_OPTION_DECL
@ -182,6 +192,11 @@ class Options {
static dart::SimpleHashMap* environment_;
#if defined(DART_PRECOMPILED_RUNTIME)
static char** env_argv_;
static int env_argc_;
#endif // defined(DART_PRECOMPILED_RUNTIME)
// Frontend argument processing.
#if !defined(DART_PRECOMPILED_RUNTIME)
static DFE* dfe_;