diff --git a/pkg/dartdev/lib/dartdev.dart b/pkg/dartdev/lib/dartdev.dart index e7402a78fa5..79e730e6291 100644 --- a/pkg/dartdev/lib/dartdev.dart +++ b/pkg/dartdev/lib/dartdev.dart @@ -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 runDartdev(List 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); diff --git a/pkg/dartdev/lib/src/vm_interop_handler.dart b/pkg/dartdev/lib/src/vm_interop_handler.dart index ccb30151085..9a630ce7472 100644 --- a/pkg/dartdev/lib/src/vm_interop_handler.dart +++ b/pkg/dartdev/lib/src/vm_interop_handler.dart @@ -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 = [_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; } diff --git a/pkg/dartdev/test/environment_test.dart b/pkg/dartdev/test/environment_test.dart new file mode 100644 index 00000000000..978ac616c6f --- /dev/null +++ b/pkg/dartdev/test/environment_test.dart @@ -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')); + }); + }); +} diff --git a/pkg/dartdev/test/utils.dart b/pkg/dartdev/test/utils.dart index fa9146ece02..4d8f3a70ead 100644 --- a/pkg/dartdev/test/utils.dart +++ b/pkg/dartdev/test/utils.dart @@ -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); } diff --git a/runtime/bin/dartdev.cc b/runtime/bin/dartdev.cc index 2979ed7159e..fe6f445a6da 100644 --- a/runtime/bin/dartdev.cc +++ b/runtime/bin/dartdev.cc @@ -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(); } diff --git a/runtime/bin/platform.h b/runtime/bin/platform.h index 9c744d5fbae..4ac6a2737ab 100644 --- a/runtime/bin/platform.h +++ b/runtime/bin/platform.h @@ -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_; diff --git a/runtime/bin/platform_linux.cc b/runtime/bin/platform_linux.cc index 86a6dc83cbb..42512e19aa1 100644 --- a/runtime/bin/platform_linux.cc +++ b/runtime/bin/platform_linux.cc @@ -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 diff --git a/runtime/bin/platform_macos.cc b/runtime/bin/platform_macos.cc index 41b4fb3d616..96272c64e3d 100644 --- a/runtime/bin/platform_macos.cc +++ b/runtime/bin/platform_macos.cc @@ -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 diff --git a/runtime/bin/platform_win.cc b/runtime/bin/platform_win.cc index 06f8f9bb7ca..74201f16ef1 100644 --- a/runtime/bin/platform_win.cc +++ b/runtime/bin/platform_win.cc @@ -430,6 +430,19 @@ void Platform::SetCoreDumpResourceLimit(int value) { // Not supported. } +bool Platform::SetEnvironmentVariable(const char* name, const char* value) { + std::unique_ptr 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 value_w = Utf8ToWideChar(value); + return ::SetEnvironmentVariableW(name_w.get(), value_w.get()) != 0; + } + return true; +} + } // namespace bin } // namespace dart