[ CLI ] Add support for modifying the environment from package:dartdev

This change makes it possible to set environment variables for the
current process from package:dartdev.

As a proof of concept, package:dartdev now sets `DART_ROOT` to the path
of the Dart SDK in the environment.

Related to https://github.com/dart-lang/sdk/issues/63210 and https://github.com/dart-lang/sdk/issues/62876

TEST=pkg/dartdev/test/environment_test.dart
Change-Id: If3a90279e99dadaba435ae3e43a752dcfda69227
Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/499300
Reviewed-by: Phil Quitslund <pquitslund@google.com>
Reviewed-by: Alexander Aprelev <aam@google.com>
This commit is contained in:
Ben Konyi
2026-05-05 10:03:41 -07:00
parent 5c3fb10fea
commit c5a57427d1
9 changed files with 122 additions and 3 deletions
+3
View File
@@ -36,6 +36,7 @@ import 'src/commands/tooling_daemon.dart';
import 'src/commands/uninstall.dart';
import 'src/core.dart';
import 'src/experiments.dart';
import 'src/sdk.dart';
import 'src/unified_analytics.dart';
import 'src/utils.dart';
import 'src/vm_interop_handler.dart';
@@ -46,6 +47,8 @@ Future<void> runDartdev(List<String> args, SendPort? port) async {
int? exitCode = 1;
try {
VmInteropHandler.initialize(port);
// Set the DART_ROOT environment variable to the SDK path.
VmInteropHandler.setEnvironmentVariable('DART_ROOT', sdk.sdkPath);
// Call the runner to execute the command; see DartdevRunner.
final runner = DartdevRunner(args, vmArgs: io.Platform.executableArguments);
exitCode = await runner.run(args);
+13 -2
View File
@@ -1,4 +1,4 @@
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// Copyright (c) 2020, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@@ -72,6 +72,16 @@ abstract class VmInteropHandler {
port.send(message);
}
/// Sets the environment variable [name] to [value] for the current process.
///
/// If [value] is null, the environment variable is removed.
static void setEnvironmentVariable(String name, String? value) {
final port = _port;
if (port == null) return;
final message = <dynamic>[_kResultSetEnvironmentVariable, name, value];
port.send(message);
}
/// This code is identical to the one in process_patch.dart, please ensure
/// changes made here are also done in process_patch.dart.
/// TODO : figure out if this functionality can be abstracted out to a
@@ -127,10 +137,11 @@ abstract class VmInteropHandler {
return result;
}
// Note: keep in sync with runtime/bin/dartdev_isolate.h
// Note: keep in sync with runtime/bin/dartdev.cc
static const int _kResultRun = 1;
static const int _kResultRunExec = 2;
static const int _kResultExit = 3;
static const int _kResultSetEnvironmentVariable = 4;
static SendPort? _port;
}
+59
View File
@@ -0,0 +1,59 @@
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
import 'dart:io';
import 'package:dartdev/src/sdk.dart';
import 'package:test/test.dart';
import 'utils.dart';
void main() {
ensureRunFromSdkBinDart();
group('Environment modification', () {
test('run command sets DART_ROOT', () async {
final p = project(
mainSrc: '''
import 'dart:io';
void main() {
print('DART_ROOT: \${Platform.environment['DART_ROOT']}');
}
''',
);
final result = await p.run(['run', p.relativeFilePath]);
expect(result.exitCode, 0);
// The environment variable was set in the parent isolate (dartdev)
// via VmInteropHandler.setEnvironmentVariable before the run command
// spawned the new isolate.
expect(result.stdout, contains('DART_ROOT: ${sdk.sdkPath}'));
});
test('run command does not overwrite existing DART_ROOT', () async {
final p = project(
mainSrc: '''
import 'dart:io';
void main() {
print('DART_ROOT: \${Platform.environment['DART_ROOT']}');
}
''',
);
final result = await Process.run(
Platform.resolvedExecutable,
['run', p.relativeFilePath],
workingDirectory: p.dir.path,
environment: {
'PUB_CACHE': p.pubCachePath,
'DART_ROOT': 'original_value',
},
);
expect(result.exitCode, 0);
// The environment variable was already set to 'original_value',
// so dartdev should not overwrite it.
expect(result.stdout, contains('DART_ROOT: original_value'));
});
});
}
+3 -1
View File
@@ -198,7 +198,9 @@ class TestProject {
Platform.resolvedExecutable,
[...arguments],
workingDirectory: workingDir ?? dir.path,
environment: {'PUB_CACHE': pubCachePath},
environment: {
'PUB_CACHE': pubCachePath,
},
)..then((p) => _process = p);
}
+15
View File
@@ -429,6 +429,7 @@ class DartDev {
DartDev_Result_Run = 1,
DartDev_Result_RunExec = 2,
DartDev_Result_Exit = 3,
DartDev_Result_SetEnvironmentVariable = 4,
} DartDev_Result;
static CStringUniquePtr ResolvedDartVmPath() {
@@ -734,6 +735,16 @@ class DartDev {
}
}
static void SetEnvironmentVariableCallback(Dart_CObject* message) {
ASSERT(GetArrayItem(message, 1)->type == Dart_CObject_kString);
const char* name = GetArrayItem(message, 1)->value.as_string;
const char* value = nullptr;
if (GetArrayItem(message, 2)->type == Dart_CObject_kString) {
value = GetArrayItem(message, 2)->value.as_string;
}
Platform::SetEnvironmentVariable(name, value);
}
// Callback that processes the result from execution of dartdev
//
static void ResultCallback(Dart_Port dest_port_id, Dart_CObject* message) {
@@ -754,6 +765,10 @@ class DartDev {
ExitResultCallback(message);
break;
}
case DartDev_Result_SetEnvironmentVariable: {
SetEnvironmentVariableCallback(message);
break;
}
default:
UNREACHABLE();
}
+2
View File
@@ -110,6 +110,8 @@ class Platform {
static void SetCoreDumpResourceLimit(int value);
static bool SetEnvironmentVariable(const char* name, const char* value);
private:
// The path to the executable.
static const char* executable_name_;
+7
View File
@@ -215,6 +215,13 @@ void Platform::SetCoreDumpResourceLimit(int value) {
setrlimit(RLIMIT_CORE, &limit);
}
bool Platform::SetEnvironmentVariable(const char* name, const char* value) {
if (value == nullptr) {
return unsetenv(name) == 0;
}
return setenv(name, value, 0) == 0;
}
} // namespace bin
} // namespace dart
+7
View File
@@ -408,6 +408,13 @@ void Platform::SetCoreDumpResourceLimit(int value) {
setrlimit(RLIMIT_CORE, &limit);
}
bool Platform::SetEnvironmentVariable(const char* name, const char* value) {
if (value == nullptr) {
return unsetenv(name) == 0;
}
return setenv(name, value, 0) == 0;
}
} // namespace bin
} // namespace dart
+13
View File
@@ -430,6 +430,19 @@ void Platform::SetCoreDumpResourceLimit(int value) {
// Not supported.
}
bool Platform::SetEnvironmentVariable(const char* name, const char* value) {
std::unique_ptr<wchar_t[]> name_w = Utf8ToWideChar(name);
if (value == nullptr) {
return ::SetEnvironmentVariableW(name_w.get(), nullptr) != 0;
}
DWORD ret = ::GetEnvironmentVariableW(name_w.get(), nullptr, 0);
if (ret == 0 && ::GetLastError() == ERROR_ENVVAR_NOT_FOUND) {
std::unique_ptr<wchar_t[]> value_w = Utf8ToWideChar(value);
return ::SetEnvironmentVariableW(name_w.get(), value_w.get()) != 0;
}
return true;
}
} // namespace bin
} // namespace dart