From 89cf1ea7800326eaa8d8447d3c6d9d8ed46996f8 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 4 Oct 2025 14:52:12 +0200 Subject: [PATCH 1/5] Add `--debug-builders` flag --- build_runner/CHANGELOG.md | 2 ++ build_runner/lib/src/bootstrap/bootstrapper.dart | 2 ++ build_runner/lib/src/bootstrap/processes.dart | 2 ++ build_runner/lib/src/build_runner.dart | 7 +++++-- build_runner/lib/src/build_runner_command_line.dart | 13 ++++++++++++- 5 files changed, 23 insertions(+), 3 deletions(-) diff --git a/build_runner/CHANGELOG.md b/build_runner/CHANGELOG.md index 7c0dfb636..29dd0f5da 100644 --- a/build_runner/CHANGELOG.md +++ b/build_runner/CHANGELOG.md @@ -1,6 +1,8 @@ ## 2.9.1-wip - Internal changes for `build_test`. +- Add the `debug-builders` flag. It starts builders in a paused state and + prints a VM service URL, allowing debuggers to attach. ## 2.9.0 diff --git a/build_runner/lib/src/bootstrap/bootstrapper.dart b/build_runner/lib/src/bootstrap/bootstrapper.dart index d93a14996..073fc4adb 100644 --- a/build_runner/lib/src/bootstrap/bootstrapper.dart +++ b/build_runner/lib/src/bootstrap/bootstrapper.dart @@ -41,6 +41,7 @@ class Bootstrapper { /// they do not exist or have the wrong types. Future run( BuiltList arguments, { + bool debug = false, Iterable? experiments, }) async { while (true) { @@ -67,6 +68,7 @@ class Bootstrapper { script: entrypointDillPath, arguments: arguments, message: buildProcessState.serialize(), + debug: debug, ); buildProcessState.deserializeAndSet(result.message); final exitCode = result.exitCode; diff --git a/build_runner/lib/src/bootstrap/processes.dart b/build_runner/lib/src/bootstrap/processes.dart index 0a6300572..21d5b479e 100644 --- a/build_runner/lib/src/bootstrap/processes.dart +++ b/build_runner/lib/src/bootstrap/processes.dart @@ -26,9 +26,11 @@ class ParentProcess { required String script, required Iterable arguments, required String message, + required bool debug, }) async { final process = await _startWithReaper(Platform.resolvedExecutable, [ 'run', + if (debug) ...['--observe', '--pause-isolates-on-start'], script, ...arguments, ]); diff --git a/build_runner/lib/src/build_runner.dart b/build_runner/lib/src/build_runner.dart index 1bbab13df..4a02b51d7 100644 --- a/build_runner/lib/src/build_runner.dart +++ b/build_runner/lib/src/build_runner.dart @@ -74,7 +74,9 @@ class BuildRunner { as String; if (commandLine.type.requiresBuilders && builderFactories == null) { - return await _runWithBuilders(); + return await _runWithBuilders( + debugBuilders: commandLine.debugBuilders == true, + ); } BuildRunnerCommand command; @@ -155,13 +157,14 @@ class BuildRunner { /// /// The nested `build_runner` invocation reaches [run] with [builderFactories] /// set, so it runs the command instead of bootstrapping. - Future _runWithBuilders() async { + Future _runWithBuilders({bool debugBuilders = false}) async { buildLog.configuration = buildLog.configuration.rebuild((b) { b.mode = commandLine.type.buildLogMode; }); return await Bootstrapper().run( arguments, + debug: debugBuilders, experiments: commandLine.enableExperiments, ); } diff --git a/build_runner/lib/src/build_runner_command_line.dart b/build_runner/lib/src/build_runner_command_line.dart index 3458ab616..c2052f194 100644 --- a/build_runner/lib/src/build_runner_command_line.dart +++ b/build_runner/lib/src/build_runner_command_line.dart @@ -55,6 +55,7 @@ class BuildRunnerCommandLine { final bool? trackPerformance; final bool? symlink; final bool? verbose; + final bool? debugBuilders; static Future parse(Iterable arguments) => _CommandRunner().run(arguments); @@ -76,7 +77,8 @@ class BuildRunnerCommandLine { release = argResults.boolNamed(releaseOption), trackPerformance = argResults.boolNamed(trackPerformanceOption), symlink = argResults.boolNamed(symlinkOption), - verbose = argResults.boolNamed(verboseOption); + verbose = argResults.boolNamed(verboseOption), + debugBuilders = argResults.boolNamed(debugBuildersOption); String get usage { // Calling `usage` only works if the command has been added to a @@ -131,6 +133,7 @@ const releaseOption = 'release'; const trackPerformanceOption = 'track-performance'; const symlinkOption = 'symlink'; const verboseOption = 'verbose'; +const debugBuildersOption = 'debug-builders'; /// [CommandRunner] that returns a [BuildRunnerCommandLine] without actually /// running it. @@ -252,6 +255,14 @@ class _Build extends Command { ..addMultiOption( enableExperimentOption, help: 'A list of dart language experiments to enable.', + ) + ..addFlag( + debugBuildersOption, + defaultsTo: false, + negatable: true, + help: + 'Start the inner build script with debugging flag, allowing a ' + 'debugger to be attached to it.', ); } From 82da30a8976d66da5663fbe89638e75aed5d642d Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Sat, 4 Oct 2025 15:00:37 +0200 Subject: [PATCH 2/5] Reword --- build_runner/lib/src/build_runner_command_line.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build_runner/lib/src/build_runner_command_line.dart b/build_runner/lib/src/build_runner_command_line.dart index c2052f194..706c8bc8d 100644 --- a/build_runner/lib/src/build_runner_command_line.dart +++ b/build_runner/lib/src/build_runner_command_line.dart @@ -261,8 +261,8 @@ class _Build extends Command { defaultsTo: false, negatable: true, help: - 'Start the inner build script with debugging flag, allowing a ' - 'debugger to be attached to it.', + 'Start the inner build script in a paused state and with the VM ' + 'service enabled, allowing a debugger to be attached to it.', ); } From 7ecc1a1ed7f836a9b2a8689ff639bfc00bfc400f Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 6 Oct 2025 21:17:52 +0200 Subject: [PATCH 3/5] Rename to --dart-jit-vm-arg --- build_runner/CHANGELOG.md | 5 ++-- build_runner/README.md | 13 ++++++++- .../lib/src/bootstrap/bootstrapper.dart | 3 +- build_runner/lib/src/bootstrap/processes.dart | 8 +++-- build_runner/lib/src/build_runner.dart | 8 ++--- .../lib/src/build_runner_command_line.dart | 21 +++++++------- .../build_command_vm_arg_test.dart | 29 +++++++++++++++++++ 7 files changed, 66 insertions(+), 21 deletions(-) create mode 100644 build_runner/test/integration_tests/build_command_vm_arg_test.dart diff --git a/build_runner/CHANGELOG.md b/build_runner/CHANGELOG.md index 29dd0f5da..56563f96d 100644 --- a/build_runner/CHANGELOG.md +++ b/build_runner/CHANGELOG.md @@ -1,8 +1,9 @@ ## 2.9.1-wip - Internal changes for `build_test`. -- Add the `debug-builders` flag. It starts builders in a paused state and - prints a VM service URL, allowing debuggers to attach. +- Add the `--dart-jit-vm-arg` option. Its values are passed to `dart run` when + a build script is started in JIT mode. This allows specifying options to + attach a debugger to builders. ## 2.9.0 diff --git a/build_runner/README.md b/build_runner/README.md index b8c592170..1f6c30720 100644 --- a/build_runner/README.md +++ b/build_runner/README.md @@ -1,4 +1,4 @@ -_Questions? Suggestions? Found a bug? Please +_Questions? Suggestions? Found a bug? Please [file an issue](https://github.com/dart-lang/build/issues) or [start a discussion](https://github.com/dart-lang/build/discussions)._ @@ -203,3 +203,14 @@ targets: For advanced use cases it's possible to write your own builder. Get started with the [build package documentation](https://pub.dev/packages/build). +For testing builders, see the [`build_test` package](https://pub.dev/packages/build_test). + +## Debugging builds + +To debug the build process, note that `build_runner` spawns a child process to run +the build. +Options used to spawn this process can be customized, which allows attaching a debugger: + +```shell +dart run build_runner build --dart-jit-vm-arg=--observe --dart-jit-vm-arg=--pause-isolates-on-start +``` diff --git a/build_runner/lib/src/bootstrap/bootstrapper.dart b/build_runner/lib/src/bootstrap/bootstrapper.dart index 073fc4adb..a1df475c0 100644 --- a/build_runner/lib/src/bootstrap/bootstrapper.dart +++ b/build_runner/lib/src/bootstrap/bootstrapper.dart @@ -42,6 +42,7 @@ class Bootstrapper { Future run( BuiltList arguments, { bool debug = false, + required Iterable jitVmArgs, Iterable? experiments, }) async { while (true) { @@ -68,7 +69,7 @@ class Bootstrapper { script: entrypointDillPath, arguments: arguments, message: buildProcessState.serialize(), - debug: debug, + jitVmArgs: jitVmArgs, ); buildProcessState.deserializeAndSet(result.message); final exitCode = result.exitCode; diff --git a/build_runner/lib/src/bootstrap/processes.dart b/build_runner/lib/src/bootstrap/processes.dart index 21d5b479e..6fac55c0a 100644 --- a/build_runner/lib/src/bootstrap/processes.dart +++ b/build_runner/lib/src/bootstrap/processes.dart @@ -20,17 +20,21 @@ class ParentProcess { /// Runs Dart [script] with [arguments], sends it [message], listens for and /// returns the response. /// + /// When the underlying script is run with `dart run`, the [jitVmArgs] are + /// forwarded to the Dart VM. This can be used to e.g. start the VM with + /// debugging options. + /// /// The child process should use [ChildProcess] to communicate with the /// parent. static Future runAndSend({ required String script, required Iterable arguments, required String message, - required bool debug, + required Iterable jitVmArgs, }) async { final process = await _startWithReaper(Platform.resolvedExecutable, [ 'run', - if (debug) ...['--observe', '--pause-isolates-on-start'], + ...jitVmArgs, script, ...arguments, ]); diff --git a/build_runner/lib/src/build_runner.dart b/build_runner/lib/src/build_runner.dart index 4a02b51d7..eb3f0d7e7 100644 --- a/build_runner/lib/src/build_runner.dart +++ b/build_runner/lib/src/build_runner.dart @@ -74,9 +74,7 @@ class BuildRunner { as String; if (commandLine.type.requiresBuilders && builderFactories == null) { - return await _runWithBuilders( - debugBuilders: commandLine.debugBuilders == true, - ); + return await _runWithBuilders(); } BuildRunnerCommand command; @@ -157,14 +155,14 @@ class BuildRunner { /// /// The nested `build_runner` invocation reaches [run] with [builderFactories] /// set, so it runs the command instead of bootstrapping. - Future _runWithBuilders({bool debugBuilders = false}) async { + Future _runWithBuilders() async { buildLog.configuration = buildLog.configuration.rebuild((b) { b.mode = commandLine.type.buildLogMode; }); return await Bootstrapper().run( arguments, - debug: debugBuilders, + jitVmArgs: commandLine.jitVmArgs ?? const Iterable.empty(), experiments: commandLine.enableExperiments, ); } diff --git a/build_runner/lib/src/build_runner_command_line.dart b/build_runner/lib/src/build_runner_command_line.dart index 706c8bc8d..cd95c4adc 100644 --- a/build_runner/lib/src/build_runner_command_line.dart +++ b/build_runner/lib/src/build_runner_command_line.dart @@ -45,6 +45,7 @@ class BuildRunnerCommandLine { final String? config; final BuiltList? defines; final BuiltList? enableExperiments; + final BuiltList? jitVmArgs; final String? hostname; final bool? liveReload; final String? logPerformance; @@ -55,7 +56,6 @@ class BuildRunnerCommandLine { final bool? trackPerformance; final bool? symlink; final bool? verbose; - final bool? debugBuilders; static Future parse(Iterable arguments) => _CommandRunner().run(arguments); @@ -68,6 +68,7 @@ class BuildRunnerCommandLine { config = argResults.stringNamed(configOption), defines = argResults.listNamed(defineOption), enableExperiments = argResults.listNamed(enableExperimentOption), + jitVmArgs = argResults.listNamed(dartJitVmArgOption), hostname = argResults.stringNamed(hostnameOption), liveReload = argResults.boolNamed(liveReloadOption), logPerformance = argResults.stringNamed(logPerformanceOption), @@ -77,8 +78,7 @@ class BuildRunnerCommandLine { release = argResults.boolNamed(releaseOption), trackPerformance = argResults.boolNamed(trackPerformanceOption), symlink = argResults.boolNamed(symlinkOption), - verbose = argResults.boolNamed(verboseOption), - debugBuilders = argResults.boolNamed(debugBuildersOption); + verbose = argResults.boolNamed(verboseOption); String get usage { // Calling `usage` only works if the command has been added to a @@ -123,6 +123,7 @@ const configOption = 'config'; const defineOption = 'define'; const deleteFilesByDefaultOption = 'delete-conflicting-outputs'; const enableExperimentOption = 'enable-experiment'; +const dartJitVmArgOption = 'dart-jit-vm-arg'; const hostnameOption = 'hostname'; const liveReloadOption = 'live-reload'; const logPerformanceOption = 'log-performance'; @@ -133,7 +134,6 @@ const releaseOption = 'release'; const trackPerformanceOption = 'track-performance'; const symlinkOption = 'symlink'; const verboseOption = 'verbose'; -const debugBuildersOption = 'debug-builders'; /// [CommandRunner] that returns a [BuildRunnerCommandLine] without actually /// running it. @@ -256,13 +256,14 @@ class _Build extends Command { enableExperimentOption, help: 'A list of dart language experiments to enable.', ) - ..addFlag( - debugBuildersOption, - defaultsTo: false, - negatable: true, + ..addMultiOption( + dartJitVmArgOption, help: - 'Start the inner build script in a paused state and with the VM ' - 'service enabled, allowing a debugger to be attached to it.', + 'Flags to pass to `dart run` when launching the inner build ' + 'script\n.' + 'For example, `--dart-jit-vm-arg "--observe" ' + '--dart-jit-vm-arg "--pause-isolates-on-start"` would start the ' + 'build script with a debugger attached to it.', ); } diff --git a/build_runner/test/integration_tests/build_command_vm_arg_test.dart b/build_runner/test/integration_tests/build_command_vm_arg_test.dart new file mode 100644 index 000000000..ce9aa9159 --- /dev/null +++ b/build_runner/test/integration_tests/build_command_vm_arg_test.dart @@ -0,0 +1,29 @@ +// Copyright (c) 2025, 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 'package:test/test.dart'; + +import '../common/common.dart'; + +void main() { + test('passing custom vm args to build script', () async { + final pubspecs = await Pubspecs.load(); + final tester = BuildRunnerTester(pubspecs); + + tester.writePackage( + name: 'root_pkg', + dependencies: ['build_runner'], + files: {}, + ); + + // This should lauch the inner build script with dart run --help ..., so we + // check that help output is emitted to verify that the option is respected. + final output = await tester.run( + 'root_pkg', + 'dart run build_runner build --dart-jit-vm-arg=--help', + ); + + expect(output, contains('Run "dart help" to see global options.')); + }); +} From c2fbb842bbc8e23c9fa587a2cb0973d96bd39211 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Mon, 6 Oct 2025 21:19:12 +0200 Subject: [PATCH 4/5] Remove unused flag --- build_runner/lib/src/bootstrap/bootstrapper.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/build_runner/lib/src/bootstrap/bootstrapper.dart b/build_runner/lib/src/bootstrap/bootstrapper.dart index a1df475c0..431ac6fc5 100644 --- a/build_runner/lib/src/bootstrap/bootstrapper.dart +++ b/build_runner/lib/src/bootstrap/bootstrapper.dart @@ -41,7 +41,6 @@ class Bootstrapper { /// they do not exist or have the wrong types. Future run( BuiltList arguments, { - bool debug = false, required Iterable jitVmArgs, Iterable? experiments, }) async { From 8bf4a8900434b479ed85122ab3792ba6a3a05e28 Mon Sep 17 00:00:00 2001 From: Simon Binder Date: Tue, 7 Oct 2025 09:12:56 +0200 Subject: [PATCH 5/5] Fix processes integration test --- build_runner/test/integration_tests/processes_test.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build_runner/test/integration_tests/processes_test.dart b/build_runner/test/integration_tests/processes_test.dart index e81ef1454..de116b97e 100644 --- a/build_runner/test/integration_tests/processes_test.dart +++ b/build_runner/test/integration_tests/processes_test.dart @@ -34,6 +34,7 @@ Future main() async { final processResult = await ParentProcess.runAndSend( script: 'bin/child.dart', arguments: [], + jitVmArgs: [], message: 'payload'); stdout.write( 'Parent received: ' @@ -78,6 +79,7 @@ Future main() async { await ParentProcess.runAndSend( script: 'bin/child.dart', arguments: [], + jitVmArgs: [], message: 'payload'); } ''');